这是 Dart 团队的一个重要里程碑: 我们为大家带来了空安全功能的技术预览版。空安全可以帮助您避免一类通常难以发现的错误,并且还可以带来一系列的性能改进。欢迎大家体验日前发布的早期技术预览版本,我们期待您的反馈。
本文将为大家介绍 Dart 团队推出空安全的计划,并解释空安全的健全之处,以及与其他语言之间的差异。
为什么需要空安全?
void printLengths(List<File> files) {
for (var file in files) {
print(file.lengthSync());
}
}
void main() {
// 错误情况 1: 将一个空文件传递给方法的 files 参数。
printLengths(null);
// 错误情况 2: 将文件列表传递给 files,但列表中包含空项目。
printLengths([File('filename1'), File('filename2'), null]);
}
健全的空安全
空安全性能提升 19%
https://gist.github.com/a-siva/07e8a5bfa8548a3041d44d5d1c6f3a40
设计原则
在开始进行详细的空安全设计之前,Dart 团队确立了以下三个核心原则:
声明空安全变量
核心语法很简单。下面给出了一些不可空变量,但通过不同的方式声明。请记住,不可空是默认的,所以这些声明虽然看起来和我们今天常见的形式一样,但其含义已经不同。
// 在空安全的 Dart 中,下列变量皆不可空。
var i = 42;
final b = Foo();
String m = '';
Dart 会确保您永远无法将空值赋予上述任何一个变量,如果您试图在一千行代码之后输入 "i = null",就会触发静态分析错误并看到红色的波浪下划线,程序也将无法编译。
如果您希望变量是可空的,您可以使用 "?",比如:
// 下列变量可空。
int? j = 1; // 之后可被赋予 null 值。
final Foo? c = getFoo(); // 方法可以返回 null。
String? n; // 初始即为 null,之后任何时间也可以为 null。
上面这几个变量的行为会与如今所有变量的行为一致。
您也可以在其他地方使用 "?" 语法:
// 在函数参数里使用。
void boogie(int? count) {
// count 可以为 null。
}
// 在函数返回值使用。
Foo? getFoo() {
// 可以返回 null 值。
}
// 也可以在泛型 (generics)、函数别名 (typedefs)、类型检查 (type checks) 等处使用。
// 上述用法均可混合使用。
但我们要再强调一遍,我们的目标是让您几乎不再需要使用 "?",您的绝大多数类型都将是不可空的。
让空安全更加易用
void honk(int? loudness) {
if (loudness == null) {
// 在没有给出 loudness 值时直接用最大音量告知开发者。
_playSound('error.wav', volume: 11);
return;
}
// Loudness 不为空,直接将其调整 (clamp) 至可接受的范围。
_playSound('honk.wav', volume: loudness.clamp(0, 11));
}
需要强调的是,Dart 对这段代码的处理方式相当 "机智",通过分析它知道在 if 语句通过之后,loudness 这个变量就不可能为空值,因此如代码的第 8 行所示,它可以让我们直接调用 loudness 的 clamp() 方法,而无需额外的操作。这种便利性是通过流程分析 (flow analysis) 来实现的: Dart 分析器会像执行代码一样检查您的代码,自动找出代码中附加的信息。
下面还有一个例子。Dart 可以确定一个变量非空,因为我们总是给它分配一个非空值:
int sign(int x) {
// result 不可空。
int result;
if (x >= 0) {
result = 1;
} else {
result = -1;
}
// 运行到这里 Dart 就知道 result 不会为 null。
return result;
}
如果您删除了上例中任意赋值语句 (例如删除 result = -1; 这一行),Dart 将无法保证 result 非空: 您会看到一个静态错误,代码则无法编译。
流程分析只在函数内部有效。如果您有全局变量或类字段,那么 Dart 无法保证什么时候会分配什么值给它。Dart 无法对整个应用的流程进行建模。出于这个原因,您可以使用新的 "late" 关键字: 您在第一次读取某个变量之前就知道它不会为空,但您不能立刻初始化它。
class Goo {
late Viscosity v;
Goo(Material m) {
v = m.computeViscosity();
}
}
空安全支持向下兼容
Dart 团队通过一年多的努力将空安全推进至技术预览版。这是自 Dart 2 推出以来,我们对 Dart 语言最大的扩充。但这个变化却没有任何破坏性。现有代码可以调用使用了空安全的代码,反之亦然。即使在空安全正式可用之后,它也将是可选功能,您可以在准备好之后再采用它。您现有的代码不需要任何修改就可以继续运行。
我们最近将 Dart 核心库全部迁移至空安全。作为空安全向下兼容的展示,我们直接替换了既有的运行在 Dart 和 Flutter 测试环境中的测试和示例应用中的核心库,而且没有引发任何问题。我们甚至将新的核心库发给许多 Google 内部的开发者,直接用于他们的生产环境,也没有出现任何问题。我们计划在该功能正式推出时,将所有 package 和应用迁移到空安全。我们希望您也这么做,但您完全可以根据自身节奏,逐个对 package 和应用进行迁移。
空安全发布时间表
我们计划逐步推出空安全功能,分为三步:
dev 渠道
https://dart.cn/get-dart#release-channels
提交反馈
https://github.com/dart-lang/sdk/issues/new?title=Null%20safety%20feedback:%20[issue%20summary]&labels=NNBD&body=Describe%20the%20issue%20or%20potential%20improvement%20in%20detail%20here
实验状态
https://github.com/dart-lang/sdk/blob/master/docs/process/experimental-flags.md
pub.dev
https://pub.flutter-io.cn
如果一切顺利的话,我们计划在年底前发布空安全功能的稳定版本。在此之前,我们将推出一些工具帮助您的代码实现空安全,包括:
一款迁移工具,帮助您在升级现有 package 和应用时,在很多步骤中实现自动化。
在 pub.dev 中提供标识,这样您就能知道某个 package 是否支持空安全。
对 pub outdated 命令进行扩展,让您可以查找支持空安全的最新版本依赖。
即刻上手体验
想要试用空安全,最快的方法是访问 nullsafety.dartpad.cn,这是一个启用了空安全的 DartPad 版本。打开 Learn with Snippets 下拉菜单即可找到一系列的练习项目,里面包含新的语法介绍和空安全的基础知识。
您也可以在小型命令行应用中尝试空安全 (我们还没有迁移像 Flutter 这样的大型框架)。您可以先下载一份 dev 渠道的 Dart SDK,然后下载这个 Dart CLI 示例应用 (可以在 GitHub repo 里找到,对应的 zip 包在这里)。示例应用的 README 文件中说明了如何通过空安全实验标识运行应用。示例中的其他文件提供了启动配置,供您在 VS Code 和 Android Studio 中进行调试。
我们为大家准备了一些文档,之后会进一步进行扩充:
空安全指引
https://dart.cn/null-safety
空安全核心库 API 文档
https://api.dart.cn/dev/2.9.0-13.0.dev/index.html
推荐阅读