声空安全和Dart FFI船到稳定的通道。
原文作者:medium.com/@mit.mit
发布地址:2021年3月4日-10分钟阅读
今天我们要宣布Dart 2.12,具有稳定版本的声音空值安全和Dart FFI。空值安全是我们最新的主要生产力功能,旨在帮助你避免空值错误,这是一类通常很难发现的bug,详见本视频介绍。FFI是一种互操作性机制,它可以让你调用现有的用C编程语言编写的代码,比如调用Windows Win32 APIs。Dart 2.12今天已经上市。
Dart平台的独特功能
在我们详细研究健全的空安全和FFI之前,让我们讨论一下它们如何与我们的Dart平台目标相适应。编程语言往往共享很多功能。例如,许多语言支持面向对象编程或在网络上运行。真正让语言与众不同的是它们独特的能力组合。
Dart的独特功能跨越了三个维度。
- 便携式。高效的编译器为设备生成x86和ARM机器代码,并为网络优化JavaScript。支持广泛的目标--移动设备、桌面PC、应用后台等。大量的库和包提供了跨所有平台的一致API,进一步降低了创建真正的多平台应用的成本。
- 生产力强。Dart平台支持热重载,实现了本地设备和网络的快速迭代开发。而且Dart提供了丰富的结构,如隔离和异步/等待,以处理常见的并发和事件驱动的应用模式。
- 稳健。Dart完善的空安全类型系统可以在开发过程中捕获错误。而且整体平台具有很高的可扩展性和可靠性,大量的应用已经在生产中使用了十多年,包括Google Ads和Google Assistant等关键业务应用。
健全的null安全使类型系统更加强大,并实现更好的性能。Dart FFI让您可以使用现有的C库,以获得更好的可移植性,并让您可以选择使用高度调整的C代码来完成性能关键型任务。
健全的空值安全
声音空值安全是自Dart 2.0中引入声音类型系统以来,对Dart语言最大的补充。空值安全进一步加强了类型系统,使你能够捕获空值错误,这是应用程序崩溃的常见原因。通过选择null safety,你可以在开发过程中捕捉null错误,防止在生产中出现崩溃。
健全的空值安全是围绕几个核心原则设计的。让我们重新审视一下这些原则对您作为开发人员的影响。
默认情况下不可空。对类型系统的根本性改变
在null安全之前,核心的挑战是你无法区分预期被传递null的代码和无法使用null的代码之间的区别。几个月前,我们在Flutter主通道中发现了一个bug,各种flutter工具命令会在某些机器配置上崩溃,出现null错误。The method '>=' was called on null上被调用。根本问题是这样的代码。
final int major = version?.major;
final int minor = version?.minor;
if (globals.platform.isMacOS) {
// plugin path of Android Studio changed after version 4.1.
if (major >= 4 && minor >= 1) {
...
你能发现这个错误吗?因为version可以为空,major和minor也都可以为空。这个bug在这里单独看来似乎很容易发现,但实际上像这样的代码一直在溜走,即使是像Flutter repo中使用的严格的代码审查过程。有了null安全,静态分析就能立即发现这个问题。(在DartPad中实战试试。)
在IDE中的分析输出截图
这是个很简单的错误。在我们早期在Google内部的代码中使用null safety的过程中,我们看到了更复杂的错误被发现。其中有些错误是多年前就知道的,但如果没有null safety的额外静态检查,团队就无法找到原因。下面是几个例子。
-
一个内部团队发现他们经常检查那些永远不会是空的表达式的空值。这个问题最常出现在使用protobuf的代码中,其中可选字段在解集时返回一个默认值,而永远不会为空。结果,代码错误地检查默认条件,通过混淆默认值和空值。
-
Google Pay团队在他们的Flutter代码中发现了错误,当他们试图在
Widget的上下文之外访问FlutterState对象时,他们会失败。在null安全之前,这些对象会返回null,并掩盖错误;在null安全之后,声音分析确定这些属性永远不可能是null,并抛出了一个分析错误。 -
Flutter团队发现了一个bug,如果在
Window.render()中向scene参数传递null,Flutter引擎可能会崩溃。在null安全迁移过程中,他们添加了一个提示,将Scene标记为非nullable,然后能够轻松防止null可能引发的应用崩溃。
在默认情况下使用non-nullable
一旦你启用了空值安全,变量声明的基本原理就会改变,因为默认类型是不可空的。
// In null-safe Dart, none of these can ever be null.
var i = 42; // Inferred to be an int.
String name = getFileName();
final b = Foo();
如果你想创建一个可以包含值或空值的变量,你需要在变量声明中通过在类型中添加一个后缀来明确这一点。
// aNullableInt can hold either an integer or null.
int? aNullableInt = null;
空值安全的实现是强大的,丰富的静态流分析使其更容易处理可空类型。例如,在检查完null之后,Dart将局部变量的类型从nullable提升为non-nullable。
int definitelyInt(int? aNullableInt) {
if (aNullableInt == null) {
return 0;
}
// aNullableInt has now promoted to a non-null int.
return aNullableInt;
}
我们还添加了一个新的关键字,required。当一个命名的参数被标记为required(在Flutter widget API中经常发生),而调用者忘记提供参数时,就会发生分析错误。
渐进式迁移到无效安全
因为null safety对我们的打字系统来说是一个根本性的改变,如果我们坚持强行采用,那将会造成极大的破坏。为了让你在合适的时候做出决定,null safety是一个可选择的功能:你可以使用Dart 2.12,而不需要被迫启用null safety。你甚至可以依赖已经启用了null safety的软件包,无论你的应用程序或软件包是否已经启用了null safety。
为了帮助你将现有的代码迁移到空安全,我们提供了一个迁移工具和一个迁移指南。该工具首先会分析您的所有现有代码。然后,您可以交互式地查看该工具推断出的空性属性。如果你不同意工具的任何结论,你可以添加无效性提示来改变推断。添加一些迁移提示对迁移质量有很大的影响。
目前,使用 dart create 和 flutter create 创建的新包和应用程序无法启用 sound null 安全性。我们希望在未来的稳定版本中改变这种情况,当我们看到大部分的生态系统已经迁移。您可以使用dart migrate在新创建的包或应用程序中轻松启用null安全。
Dart生态系统的空安全迁移状态
在过去的一年里,我们提供了几个sound null safety的预览版和测试版构建,目标是为生态系统播种支持null safety的包。这个准备工作很重要,因为我们建议按顺序迁移到sound null safety--在其所有依赖关系已经迁移之前,你不应该迁移一个包或应用。
我们已经发布了由Dart、Flutter、Firebase和Material团队提供的数百个包的空安全版本。我们也看到了来自令人惊叹的Dart和Flutter生态系统的巨大支持,因此pub.dev现在有超过1000个包支持空安全。重要的是,最受欢迎的包已经率先迁移,因此,在今天的发布会上,98%的前100名最受欢迎的包、78%的前250名和57%的前500名已经及时支持了null安全。我们期待着在未来几周内,在pub.dev上看到更多具有null安全性的软件包。我们的分析显示,pub.dev上的绝大多数软件包都已经解封,可以开始迁移。
完全健全的null安全的好处
一旦你完全迁移了,Dart的null安全是健全的。这意味着,Dart 100%确定具有非空值类型的表达式不能是空值。当Dart分析你的代码并确定一个变量是不可空的,这个变量总是不可空的。Dart与Swift共享健全的空值安全,但其他编程语言并不多。 Dart健全的空值安全还有另一个可喜的含义:它意味着你的程序可以更小更快。因为Dart确信非空值变量永远不会为空,所以Dart可以进行优化。例如,Dart超前(AOT)编译器可以产生更小更快的原生代码,因为当它知道一个变量不是空的时候,它不需要添加对空的检查。
Dart FFI用于整合Dart与C库。
Dart FFI使你能够利用C库中现有的代码,既可以更好地移植,也可以与高度调整的C代码集成,完成性能关键任务。从Dart 2.12开始,Dart FFI已经脱离了测试阶段,现在被认为是稳定的,可以用于生产。我们还增加了一些新的功能,包括嵌套结构和按值传递结构。
按值传递结构
在C代码中,结构既可以通过引用传递,也可以通过值传递。FFI以前只支持通过引用传递,但是从Dart 2.12开始,你可以通过值传递结构。下面是两个C函数的小例子,它们既可以通过引用传递,也可以通过值传递。
struct Link {
double value;
Link* next;
};
void MoveByReference(Link* link) {
link->value = link->value + 10.0;
}
Coord MoveByValue(Link link) {
link.value = link.value + 10.0;
return link;
}
嵌套结构
C API经常使用嵌套结构--本身包含结构的结构,比如这个例子。
struct Wheel {
int spokes;
};
struct Bike {
struct Wheel front;
struct Wheel rear;
int buildYear;
};
从Dart 2.12开始,FFI支持嵌套结构。
API变化
作为宣布FFI稳定的一部分,并支持上述功能,我们做了一些较小的API更改。
现在不允许创建空结构(打破了#44622的变化),并产生一个废弃警告。你可以使用一个新的类型,Opaque,来表示空结构。dart:ffi 函数 sizeOf、 elementAt 和 ref 现在需要编译时的类型参数 (打破了 #44621)。因为在 package:ffi 中增加了新的便利函数,所以在常见的情况下,不需要额外的关于分配和释放内存的模板。
// Allocate a pointer to an Utf8 array, fill it from a Dart string,
// pass it to a C function, convert the result, and free the arg.
//
// Before API change:
final pointer = allocate<Int8>(count: 10);
free(pointer);
final arg = Utf8.toUtf8('Michael');
var result = helloWorldInC(arg);
print(Utf8.fromUtf8(result);
free(arg);
// After API change:
final pointer = calloc<Int8>(10);
calloc.free(pointer);
final arg = 'Michael'.toNativeUtf8();
var result = helloWorldInC(arg);
print(result.toDartString);
calloc.free(arg);
自动生成FFI绑定
对于大型的API表面,编写与C代码集成的Dart绑定是非常耗时的。为了减少这个负担,我们已经建立了一个绑定生成器,用于从C头文件中自动创建FFI包装器。我们邀请您试用它:package:ffigen。
FFI路线图
随着核心FFI平台的完成,我们正在将重点转向扩展FFI功能集,在核心平台的基础上增加一些功能。我们正在研究的一些特性包括。
- ABI特定的数据类型,比如int, long, size_t (#36140)
- 结构中的内联数组 (#35763)
- 包装结构(#38158)
- 联盟类型(#38491)
- 将定标器暴露给Dart (#35770; 但是请注意,你已经可以使用C语言的析构器了)
FFI的使用实例
我们已经看到许多创造性的使用Dart FFI与一系列基于C的API集成。这里有几个例子。
- open_file是一个跨平台打开文件的单一API,它使用FFI在Windows,macOS和Linux上调用本地操作系统API。它使用FFI来调用Windows、macOS和Linux上的本地操作系统API。
- win32包装了大多数常见的Win32 API,使得它可以直接从Dart调用广泛的Windows API。
- objectbox是一个基于C语言实现的快速数据库。
- tflite_flutter使用FFI来包装TensorFlow Lite API。
Dart语言的下一步是什么?
声空安全是我们几年来对Dart语言最大的改变。接下来,我们将着眼于在我们强大的基础上,对语言和平台做出更多的增量变化。下面是我们在语言设计漏斗中正在试验的一些东西的快速视图。
- 类型别名(#65)。能够为非函数类型创建类型别名。例如,你可以创建一个 typedef 并将其作为变量类型使用。
typedef IntList = List<int>;
IntList il = [1,2,3];
-
三档操作符(#120)。增加一个新的、可完全覆盖的>>>操作符,用于在整数上进行无符号位移。
-
通用元数据注解(#1297)。扩展元数据注解,以支持包含类型参数的注解。
-
静态元编程 (#1482)。支持静态元编程--在编译过程中产生新的Dart源代码的Dart程序,类似于Rust宏和Swift函数构建器。这个功能还处于早期探索阶段,但我们认为它可能会实现今天依赖代码生成的用例。
Dart 2.12现已发布
Dart 2.12,具有完善的空安全和稳定的FFI,今天可以在Dart 2.12和Flutter 2.0 SDK中使用。请花点时间回顾一下Dart和Flutter的已知空安全问题。如果您发现任何其他问题,请在Dart问题跟踪器中报告它们。
如果你已经开发了发布在pub.dev上的包,请查看今天的迁移指南,并了解如何迁移到健全的空安全。迁移你的包很可能会帮助解禁其他依赖它的包和应用。我们也要向那些已经迁移的用户表示感谢!
我们很想听听你对音空安全和FFI的体验。请在下方留言或发微博@dart_lang。
通过www.DeepL.com/Translator(免费版)翻译