[Dart翻译]宣布Dart 2.12

335 阅读13分钟

声空安全和Dart FFI船到稳定的通道。

原文地址:medium.com/dartlang/an…

原文作者: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可以为空,majorminor也都可以为空。这个bug在这里单独看来似乎很容易发现,但实际上像这样的代码一直在溜走,即使是像Flutter repo中使用的严格的代码审查过程。有了null安全,静态分析就能立即发现这个问题。(在DartPad中实战试试。)

在IDE中的分析输出截图

这是个很简单的错误。在我们早期在Google内部的代码中使用null safety的过程中,我们看到了更复杂的错误被发现。其中有些错误是多年前就知道的,但如果没有null safety的额外静态检查,团队就无法找到原因。下面是几个例子。

  • 一个内部团队发现他们经常检查那些永远不会是空的表达式的空值。这个问题最常出现在使用protobuf的代码中,其中可选字段在解集时返回一个默认值,而永远不会为空。结果,代码错误地检查默认条件,通过混淆默认值和空值。

  • Google Pay团队在他们的Flutter代码中发现了错误,当他们试图在Widget的上下文之外访问Flutter State对象时,他们会失败。在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 createflutter create 创建的新包和应用程序无法启用 sound null 安全性。我们希望在未来的稳定版本中改变这种情况,当我们看到大部分的生态系统已经迁移。您可以使用dart migrate在新创建的包或应用程序中轻松启用null安全

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

在过去的一年里,我们提供了几个sound null safety的预览版和测试版构建,目标是为生态系统播种支持null safety的包。这个准备工作很重要,因为我们建议按顺序迁移到sound null safety--在其所有依赖关系已经迁移之前,你不应该迁移一个包或应用。

我们已经发布了由DartFlutterFirebaseMaterial团队提供的数百个包的空安全版本。我们也看到了来自令人惊叹的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 函数 sizeOfelementAtref 现在需要编译时的类型参数 (打破了 #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功能集,在核心平台的基础上增加一些功能。我们正在研究的一些特性包括。

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.12Flutter 2.0 SDK中使用。请花点时间回顾一下DartFlutter的已知空安全问题。如果您发现任何其他问题,请在Dart问题跟踪器中报告它们。

如果你已经开发了发布在pub.dev上的包,请查看今天的迁移指南,并了解如何迁移到健全的空安全。迁移你的包很可能会帮助解禁其他依赖它的包和应用。我们也要向那些已经迁移的用户表示感谢!

我们很想听听你对音空安全和FFI的体验。请在下方留言或发微博@dart_lang


通过www.DeepL.com/Translator(免费版)翻译