Dart 2.12 现已发布

做者 / Michael Thomsenhtml

Dart 2.12 现已发布,其中包含 健全的空安全Dart FFI 的稳定版。空安全是咱们最新主打的一项生产力强化功能,意在帮助您规避空值错误,之前这种错误一般很难被发现,您能够观看下面这支视频了解详情。FFI 则是一种互操做机制,支持调用以 C 语言编写的既有代码,例如调用 Windows Win32 API。欢迎你们即刻开始使用 Dart 2.12。git

https://www.bilibili.com/vide...github

Dart 平台的独特功能

在详细了解健全空安全和 FFI 以前,咱们先来讨论一下它们在哪些方面契合了咱们对 Dart 平台的指望。编程语言每每有不少相似的功能,例如,不少语言都支持面向对象的编程或在 web 上运行。真正将各个语言区分开来的,是其独特的功能组合。web

Dart 具备横跨三个维度的独特功能组合:数据库

  • 可移植性: 高效的编译器可针对设备生成 x86 和 ARM 机器代码,并针对 web 生成优化的 JavaScript。同时兼容移动设备、桌面 PC、应用后端等多种 目标平台。大量的开发库和 package 提供了可在全部平台上使用的一致的 API,进一步下降了开发者建立真正多平台应用的成本。
  • 高生产力: Dart 平台支持热重载,所以可在原生设备和 web 上实现快速迭代开发。此外,Dart 还提供了丰富的结构,如 isolates 和 async/await 等,用以处理和实现常见的并发和事件驱动的应用模式。
  • 稳健: Dart 的健全空安全类型系统能够在开发过程当中就捕捉到错误。整个平台拥有极好的可扩展性和可靠性,已经被大量且多样的应用在累计超过十年的生产环境中实战检验过,其中包括 Google 的一些关键业务应用,如 Google Ads 和 Google Assistant 等。

健全空安全加强了类型系统的稳健性,同时提升了性能。借助 Dart FFI,您能够得到更强的可移植性,同时沿用由 C 语言编写的既有代码,在处理对性能要求极为严苛的任务时,能够尽情使用通过精心优化的 C 语言代码。编程

健全的空安全

Dart 2.0 中引入健全类型系统以来,Dart 语言中最重大的新增内容即是健全空安全。空安全进一步加强了类型系统,让您可以捕捉到空值错误,此类错误常常致使应用崩溃。启用空安全后,您就能够在开发过程当中捕捉到空值错误,避免应用在生产环境中发生崩溃。swift

健全空安全的设计围绕一套核心原则展开。您能够阅读 官方文档 了解这些原则对开发者的影响。后端

默认不可空: 从根本改变类型系统

在空安全出现以前,开发者面临的核心挑战在于没法区分预期收到空值的代码和不接受空值的代码。几个月前,咱们在 Flutter 的 master 渠道中发现了一个错误,多个 flutter 工具命令在特定计算机配置下会发生崩溃,并触发空值错误: 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 可能为空,因此 majorminor 也可能为空。若是单独检查此处代码,这一错误彷佛并不难发现。但实际上,即便通过了严格的代码审查过程 (如 Flutter repo 所采用的代码审查流程),也老是不免有这样的漏网之鱼。在启用空安全后,静态分析可以当即捕捉到这一问题 (以下图)。您能够 在 DartPad 中亲自上手体验安全

△ IDE 中的分析结果

△ IDE 中的分析结果

这只是一个很是简单的错误。咱们早期在 Google 内部的代码中使用空安全时,捕捉到的复杂错误远多于此。其中一些是多年前就已经发现的 bug,但在经过空安全进行额外的静态检查前,不少团队都未能找到缘由。

  • 内部团队发现,他们常常检查表达式中是否存在空值,而这些表达式永远不可能为空。这个问题在使用 protobuf 的代码中最多见,其中可选字段在未经设置时会返回一个默认值,并且永不为空。这会致使代码混淆默认值和空值,并错误地检查默认条件。
  • Google Pay 团队在他们的 Flutter 代码中发现了一些 bug,在尝试访问 Widget 上下文以外的 Flutter State 对象时会出错。在采用空安全以前,这些对象会返回 null 并掩盖错误;在采用空安全以后,健全分析肯定这些属性永远不可能为空,并会给出分析错误。
  • Flutter 团队发现了一个 bug: 若是在 Window.render() 中向 scene 参数传递空值,则 Flutter 引擎可能会崩溃。在向空安全迁移的过程当中,他们添加了一个提示,将 Scene 标记为不可空,便可轻松防止空值可能引起的应用崩溃。

在默认不可空的前提下工做

启用空安全 后,声明变量的基础方法会发生变化,由于默认类型不可为空:

// 在空安全的 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。

为了帮助您将现有代码迁移至空安全,咱们提供了迁移工具和 迁移指南。该工具会首先分析您全部的代码,而后您能够交互式地查看工具推断出的可空属性,若是您不一样意工具得出的结论,则能够添加可空性提示以更改推断。添加迁移提示可能会大幅提高迁移质量。

目前,在默认状况下,使用 dart createflutter create 新建立的 package 和应用中不会启用健全空安全。在大部分生态系统完成迁移后,咱们预计将在后续的稳定版本中默认启用。您能够经过 dart migrate 在新建立的 package 或应用中轻松 启用空安全

Dart 生态系统的空安全迁移状态

去年,咱们提供了健全空安全的数个预览版和 Beta 版,旨在为生态系统提供首批支持空安全的 package。这项工做很是重要,咱们建议你们 有序迁移至健全空安全,也就是说,在全部依赖项迁移完成以前,最好不要迁移本身的 package 或应用。

咱们已发布由 DartFlutterFirebaseMaterial 团队所提供的数百个 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 可以彻底确保具备不可空类型的表达式不为空。当 Dart 分析完您的代码并肯定某个变量不可为空时,该变量将始终不可为空。Dart 与 Swift 都拥有健全的空安全,但有些编程语言在这方面仍有待改进。

Dart 的健全空安全还暗含另外一项备受期待的优点: 您的程序能够更小、更快。因为 Dart 可以确保不可为空的变量毫不为空,所以能够 实现优化。例如,Dart 的运行前 (ahead-of-time, AOT) 编译器能够生成更小更快的原生代码,由于当其知道变量不为空时,便再也不须要添加空值检查了。

Dart FFI: 集成 Dart 与 C 语言代码库

您能够经过 Dart FFI 调用 C 语言编写的既有代码库,从而加强可移植性,还能够经过精心打磨的 C 代码完成对性能要求极为严苛的任务。从 Dart 2.12 起,Dart FFI 已结束 Beta 测试阶段,现已进入稳定状态,能够用于生产环境。咱们还添加了一些新功能,包括嵌套结构和按值传递结构。

按值传递结构

在 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 中增长了新的便利函数,因此在常见的状况下,无需额外添加关于分配和释放内存的模板代码:

// 分配一个 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);

自动生成 FFI 绑定

对于大型的 API 接口,编写与 C 代码集成的 Dart 绑定极其耗时。为减轻这一负担,咱们为你们准备了绑定生成器,能够经过 C 头文件自动建立 FFI 封装代码,欢迎试用

FFI 路线图

核心 FFI 平台完成后,咱们的工做重心将转向基于核心平台扩展 FFI 功能集。咱们正在研究的一些功能包括:

  • ABI 特定数据类型,如 int、long、size_t (#36140)
  • 结构中的内联数组 (#35763)
  • Packed 结构 (#38158)
  • 联合类型 (#38491)
  • 对 Dart 开放终结方法 (finalizer) (#35770,请注意,您如今能够经过 C 语言使用终结方法)

FFI 使用示例

在过去的几个月中,咱们看到你们在使用 Dart FFI 集成一系列基于 C 语言的 API 时,发掘出了许多有创意的用法。下面介绍几个示例:

  • open_file 是一个用于在多个平台打开文件的 API,使用 FFI 在 Windows、macOS 和 Linux 上调用操做系统原生 API。https://pub.flutter-io.cn/pac...
  • win32 封装了最经常使用的 Win32 API,便于从 Dart 直接调用各类 Windows API。
  • objectbox 是一个快速数据库,底层由 C 语言实现。
  • tflite_flutter 使用 FFI 封装了 TensorFlow Lite API。

Dart 语言的将来计划

健全空安全是这几年咱们对 Dart 语言作出的最大改变。接下来,咱们将继续稳步改进 Dart 语言和平台。下面简单介绍一些咱们在 语言设计规划 中实验的内容:

  • 类型别名 (#65): 将建立类型别名的功能扩展到非函数类型。例如,您能够建立一个 typedef 并将其用做变量类型:
typedef IntList = List<int>;
IntList il = [1,2,3];
  • 无符号右移运算符 (#120): 添加新的 >>> 运算符,便于对整数执行无符号移位操做。此运算符可彻底重写。
  • 通用元数据注解 (#1297): 扩展元数据注解,以同时支持含类型参数的注解。
  • 静态元编程 (#1482): 支持静态元编程,即编译期间生成新 Dart 源代码的 Dart 程序,与 Rust 宏 (macro) 和 Swift 函数构建器 (function builder) 相似。该功能目前仍处于早期探索阶段,但咱们认为它可能会开启全新用例的大门,打破如今依赖代码生成的僵局。

Dart 2.12 现已发布

欢迎你们下载 Dart 2.12Flutter 2.0 SDK,即刻开始使用 Dart 2.12,尽情体验健全空安全和稳定版 FFI。请你们阅览 DartFlutter 的已知空安全问题。若是您发现其余任何问题,请在 Dart 问题跟踪页 中报告给咱们。

若是您已在 pub.dev 上发布了 package,请当即参阅 迁移指南,了解如何迁移至健全空安全。迁移有助于依赖您的 package 的其余 package 和应用完成迁移。咱们在此向已经完成迁移的开发者们表示感谢!

欢迎你们与咱们分享本身的健全空安全和 FFI 体验,咱们评论区见!

相关文章
相关标签/搜索