作者 / Michael Thomsen
Dart 2.12 现已发布,其中包含健全的空安全和 Dart FFI 的稳定版。空安全是我们最新主打的一项生产力强化功能,意在帮助您规避空值错误,以前这种错误通常很难被发现,您可以观看下面这支视频了解详情。FFI 则是一种互操作机制,支持调用以 C 语言编写的既有代码,例如调用 Windows Win32 API。欢迎大家即刻开始使用 Dart 2.12。
△ Dart 健全的空安全
-
健全的空安全 https://dart.cn/null-safety -
Dart FFI https://dart.cn/guides/libraries/c-interop -
Win32 API https://pub.flutter-io.cn/packages/win32 -
腾讯视频链接 https://v.qq.com/x/page/n3208kis0wj.html -
Bilibili 视频链接 https://www.bilibili.com/video/BV1GV411Y7sW/
Dart 平台的独特功能
在详细了解健全空安全和 FFI 之前,我们先来讨论一下它们在哪些方面契合了我们对 Dart 平台的期望。编程语言往往有很多类似的功能,例如,很多语言都支持面向对象的编程或在 web 上运行。真正将各个语言区分开来的,是其独特的功能组合。
Dart 具有横跨三个维度的独特功能组合:
-
可移植性: 高效的编译器可针对设备生成 x86 和 ARM 机器代码,并针对 web 生成优化的 JavaScript。同时兼容移动设备、桌面 PC、应用后端等多种目标平台。大量的开发库和 package 提供了可在所有平台上使用的一致的 API,进一步降低了开发者创建真正多平台应用的成本。
-
目标平台 https://dart.cn/overview#platform
-
高生产力: Dart 平台支持热重载,因此可在原生设备和 web 上实现快速迭代开发。此外,Dart 还提供了丰富的结构,如 isolates 和 async/await 等,用以处理和实现常见的并发和事件驱动的应用模式。
-
稳健: Dart 的健全空安全类型系统可以在开发过程中就捕捉到错误。整个平台拥有极好的可扩展性和可靠性,已经被大量且多样的应用在累计超过十年的生产环境中实战检验过,其中包括 Google 的一些关键业务应用,如 Google Ads 和 Google Assistant 等。
健全空安全增强了类型系统的稳健性,同时提高了性能。借助 Dart FFI,您可以获得更强的可移植性,同时沿用由 C 语言编写的既有代码,在处理对性能要求极为严苛的任务时,可以尽情使用经过精心优化的 C 语言代码。
健全的空安全
-
Dart 2.0
https://medium.com/dartlang/announcing-dart-2-80ba01f43b6
-
空安全的原则
https://dart.cn/null-safety#null-safety-principles
默认不可空: 从根本改变类型系统
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 也可能为空。如果单独检查此处代码,这一错误似乎并不难发现。但实际上,即使经过了严格的代码审查过程 (如 Flutter repo 所采用的代码审查流程),也总是难免有这样的漏网之鱼。在启用空安全后,静态分析能够立即捕捉到这一问题 (如下图)。您可以在 DartPad 中亲自上手体验。
-
在 DartPad 中体验空安全 https://dartpad.dev/0e9797be7488d8ec6c3fca92b7f2740f?null_safety=true
-
内部团队发现,他们经常检查表达式中是否存在空值,而这些表达式永远不可能为空。这个问题在使用 protobuf 的代码中最常见,其中可选字段在未经设置时会返回一个默认值,而且永不为空。这会导致代码混淆默认值和空值,并错误地检查默认条件。 -
Google Pay 团队在他们的 Flutter 代码中发现了一些 bug,在尝试访问 Widget 上下文之外的 Flutter State 对象时会出错。在采用空安全之前,这些对象会返回 null 并掩盖错误;在采用空安全之后,健全分析确定这些属性永远不可能为空,并会给出分析错误。 -
Flutter 团队发现了一个 bug: 如果在 Window.render() 中向 scene 参数传递空值,则 Flutter 引擎可能会崩溃。在向空安全迁移的过程中,他们添加了一个提示,将 Scene 标记为不可空,即可轻松防止空值可能引发的应用崩溃。
-
Protocol Buffers https://developers.google.cn/protocol-buffers -
将 Scene 标记为不可空 https://github.com/cbracken/engine/blob/bad869e229a8a02cad6e63d12e80807b33b5c12f/lib/ui/window.dart#L1069
-
启用空安全 https://dart.cn/null-safety#enable-null-safety
// 在空安全的 Dart 中,以下均不可为空
var i = 42; // Inferred to be an int.
String name = getFileName();
final b = Foo();
如果您想要创建可能同时包含值或 null 的变量,则需要在声明变量时在类型后面显式添加 ? 后缀:
// aNullableInt 可以为整型或 null
int? aNullableInt = null;
空安全的实现很稳健,并提供丰富的静态流程分析,方便开发者轻松处理可空类型。例如,局部变量在进行空值检查后,Dart 会将其类型从可空提升为非空:
int definitelyInt(int? aNullableInt) {
if (aNullableInt == null) {
return 0;
}
// aNullableInt 现在会被提示为非空 int
return aNullableInt;
}
我们还添加了一个新的关键字,required。当一个命名的参数被标记为 required (在 Flutter widget API 中经常出现),而调用者忘记提供该参数时,就会发生如下分析错误:
渐进迁移至空安全
空安全对于我们的类型系统而言是一项根本性的改变,因此如果我们执意强制所有开发者采用,势必会造成严重的混乱。因此,我们想让您自行决定合适的迁移时机,空安全将是一项可选特性: 在做好准备之前,您可以在无需强制启用空安全的情况下使用 Dart 2.12。您甚至可以在尚未启用空安全的应用或 package 中依赖已启用空安全的 package。
为了帮助您将现有代码迁移至空安全,我们提供了迁移工具和迁移指南。该工具会首先分析您所有的代码,然后您可以交互式地查看工具推断出的可空属性,如果您不同意工具得出的结论,则可以添加可空性提示以更改推断。添加迁移提示可能会大幅提升迁移质量。
-
迁移至空安全 https://dart.cn/null-safety/migration-guide
目前,在默认情况下,使用 dart create 和 flutter create 新创建的 package 和应用中不会启用健全空安全。在大部分生态系统完成迁移后,我们预计将在后续的稳定版本中默认启用。您可以通过 dart migrate 在新创建的 package 或应用中轻松启用空安全。
-
dart create https://dart.cn/tools/dart-tool -
flutter create https://flutter.cn/docs/reference/flutter-cli -
创建空安全版本的 package 或应用 https://dart.cn/null-safety#create
Dart 生态系统的空安全迁移状态
去年,我们提供了健全空安全的数个预览版和 Beta 版,旨在为生态系统提供首批支持空安全的 package。这项工作非常重要,我们建议大家有序迁移至健全空安全,也就是说,在所有依赖项迁移完成之前,最好不要迁移自己的 package 或应用。
-
有序迁移 https://dart.cn/null-safety/migration-guide#step1-wait
我们已发布由 Dart、Flutter、Firebase 和 Material 团队所提供的数百个 package 的空安全版本。令人惊喜的是,Dart 和 Flutter 生态系统对此也予以巨大的支持,pub.dev 现在共有 1,000 多个 package 支持空安全。而且重要的是,最受欢迎的 package 已率先完成迁移,截止到 Dart 2.12 发布时,前 100 个最受欢迎的 package 中已有 98 个支持空安全,而在前 250 和前 500 的 package 中,支持空安全的比例则为 78% 和 57%。我们希望在接下来的几周,pub.dev 上能够出现更多支持空安全的 package。我们的分析表明,pub.dev 上的绝大多数 package 已经可以开始迁移。
-
Dart packages https://pub.flutter-io.cn/packages?q=publisher%3Adart.dev&sort=popularity&null-safe=1 -
Flutter packages https://pub.flutter-io.cn/packages?q=publisher%3Aflutter.dev&sort=popularity&null-safe=1 -
Firebase packages https://pub.flutter-io.cn/packages?q=publisher%3Afirebase.google.com&sort=popularity&null-safe=1 -
Material packages https://pub.flutter-io.cn/packages?q=publisher%3Amaterial.io&sort=popularity&null-safe=1 -
空安全迁移状态 https://github.com/dart-lang/sdk/wiki/Null-safety-migration-status -
迁移至空安全 https://dart.cn/null-safety/migration-guide
完成迁移后,您的项目就处于健全的空安全模式下了。这意味着 Dart 能够完全确保具有不可空类型的表达式不为空。当 Dart 分析完您的代码并确定某个变量不可为空时,该变量将始终不可为空。Dart 与 Swift 都拥有健全的空安全,但有些编程语言在这方面仍有待改进。
Dart 的健全空安全还暗含另一项备受期待的优势: 您的程序可以更小、更快。由于 Dart 能够确保不可为空的变量绝不为空,因此可以实现优化。例如,Dart 的运行前 (ahead-of-time, AOT) 编译器可以生成更小更快的原生代码,因为当其知道变量不为空时,便不再需要添加空值检查了。
-
Dart 以及健全类型带来的性能优化 https://medium.com/dartlang/dart-and-the-performance-benefits-of-sound-types-6ceedd5b6cdc
Dart FFI: 集成 Dart 与 C 语言代码库
您可以通过 Dart FFI 调用 C 语言编写的既有代码库,从而增强可移植性,还可以通过精心打磨的 C 代码完成对性能要求极为严苛的任务。从 Dart 2.12 起,Dart FFI 已结束 Beta 测试阶段,现已进入稳定状态,可以用于生产环境。我们还添加了一些新功能,包括嵌套结构和按值传递结构。
-
Dart FFI https://dart.cn/guides/libraries/c-interop
按值传递结构
在 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;
}
嵌套结构
struct Wheel {
int spokes;
};
struct Bike {
struct Wheel front;
struct Wheel rear;
int buildYear;
};
API 改动
作为 FFI 稳定版发布内容的一部分,并且为了支持上述功能,我们做了一些小幅的 API 改动。
现在不允许创建空结构 (重要改动参照 #44622),并会给出弃用警告。您可以使用一个新的类型 Opaque 来表示空结构。dart:ffi 函数 sizeOf、elementAt 和 ref 现在需要编译时的类型参数 (重要改动参照 #44621)。因为在 package:ffi 中增加了新的便利函数,所以在常见的情况下,无需额外添加关于分配和释放内存的模板代码:
// 分配一个 Utf8 数组,使用 Dart 字符串填充,然后传递给 C 方法并转换结果,最后释放 arg
//
// API 变更前:
final pointer = allocate<Int8>(count: 10);
free(pointer);
final arg = Utf8.toUtf8('Michael');
var result = helloWorldInC(arg);
print(Utf8.fromUtf8(result);
free(arg);
// API 变更后:
final pointer = calloc<Int8>(10);
calloc.free(pointer);
final arg = 'Michael'.toNativeUtf8();
var result = helloWorldInC(arg);
print(result.toDartString);
calloc.free(arg);
-
#44622 https://github.com/dart-lang/sdk/issues/44622 -
#44621 https://github.com/dart-lang/sdk/issues/44621
自动生成 FFI 绑定
-
Package:ffigen https://pub.flutter-io.cn/packages/ffigen
FFI 路线图
核心 FFI 平台完成后,我们的工作重心将转向基于核心平台扩展 FFI 功能集。我们正在研究的一些功能包括:
-
ABI 特定数据类型,如 int、long、size_t (#36140)
https://github.com/dart-lang/sdk/issues/36140
-
结构中的内联数组 (#35763)
https://github.com/dart-lang/sdk/issues/35763 -
Packed 结构 (#38158)
https://github.com/dart-lang/sdk/issues/38158 -
联合类型 (#38491)
https://github.com/dart-lang/sdk/issues/38491 -
对 Dart 开放终结方法 (finalizer) (#35770,请注意,您现在可以通过 C 语言使用终结方法)
https://github.com/dart-lang/sdk/issues/35770
FFI 使用示例
在过去的几个月中,我们看到大家在使用 Dart FFI 集成一系列基于 C 语言的 API 时,发掘出了许多有创意的用法。下面介绍几个示例:
-
open_file 是一个用于在多个平台打开文件的 API,使用 FFI 在 Windows、macOS 和 Linux 上调用操作系统原生 API。
https://pub.flutter-io.cn/packages/open_file
-
win32 封装了最常用的 Win32 API,便于从 Dart 直接调用各种 Windows API。
https://pub.flutter-io.cn/packages/win32 -
objectbox 是一个快速数据库,底层由 C 语言实现。
https://pub.flutter-io.cn/packages/objectbox -
tflite_flutter 使用 FFI 封装了 TensorFlow Lite API。
https://pub.flutter-io.cn/packages/tflite_flutter
Dart 语言的未来计划
-
类型别名 (#65): 将创建类型别名的功能扩展到非函数类型。例如,您可以创建一个 typedef 并将其用作变量类型:
typedef IntList = List<int>;
IntList il = [1,2,3];
-
无符号右移运算符 (#120): 添加新的 >>> 运算符,便于对整数执行无符号移位操作。此运算符可完全重写。 -
通用元数据注解 (#1297): 扩展元数据注解,以同时支持含类型参数的注解。 -
静态元编程 (#1482): 支持静态元编程,即编译期间生成新 Dart 源代码的 Dart 程序,与 Rust 宏 (macro) 和 Swift 函数构建器 (function builder) 类似。该功能目前仍处于早期探索阶段,但我们认为它可能会开启全新用例的大门,打破现在依赖代码生成的僵局。
-
语言设计规划 https://github.com/dart-lang/language/projects/1 -
#65 https://github.com/dart-lang/language/issues/65 -
#120 https://github.com/dart-lang/language/issues/120 -
#1297 https://github.com/dart-lang/language/issues/1297 -
#1482 https://github.com/dart-lang/language/issues/1482 -
macros https://doc.rust-lang.org/book/ch19-06-macros.html -
function builder https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md
Dart 2.12 现已发布
欢迎大家下载 Dart 2.12 和 Flutter 2.0 SDK,即刻开始使用 Dart 2.12,尽情体验健全空安全和稳定版 FFI。请大家阅览 Dart 和 Flutter 的已知空安全问题。如果您发现其他任何问题,请在 Dart 问题跟踪页中报告给我们。
-
Dart 2.12 https://dart.cn/get-dart -
Flutter 2.0 https://flutter.cn/docs/get-started/ -
Dart 空安全问题 https://dart.cn/null-safety#known-issues -
Flutter 空安全问题 https://flutter.cn/docs/null-safety#known-issues -
Dart 问题追踪页 https://github.com/dart-lang/sdk/issues
如果您已在 pub.dev 上发布了 package,请立即参阅迁移指南,了解如何迁移至健全空安全。迁移有助于依赖您的 package 的其他 package 和应用完成迁移。我们在此向已经完成迁移的开发者们表示感谢!
-
pub.dev https://pub.dev/ -
迁移至空安全 https://dart.cn/null-safety/migration-guide
推荐阅读
文章评论