(译)Dart 2.13 类型别名、改进FFI、优化性能、Docker镜像支持

548 阅读8分钟

image.png 今天,我们宣布Dart 2.13具有类型别名,这是目前我们要求排名第二的语言功能。Dart 2.13还包括改进的Dart FFI和更好的性能,并且我们为Dart提供了新的Docker Official Images。这篇文章提供了2.12中引入的null安全功能的更新,讨论了2.13的新功能,有关DockerGoogle Cloud对Dart后端的支持的一些令人振奋的消息,并预览了您可能期望在未来版本中看到的一些更改。

空安全更新

我们在3月的Dart 2.12版本中启动了声音无效安全性。空安全性是Dart最新的主要生产力功能,旨在帮助您避免空错误-一类通常很难发现的错误。通过该发布,我们鼓励软件包发布者开始将pub.dev上的共享软件包迁移到安全性为空。 我们非常高兴地看到采用null安全性的速度有多快!发布后仅几个月,pub.dev上最流行的500个软件包中有93%已经支持null安全。我们衷心感谢所有软件包开发人员这么快地完成这项工作,并帮助整个生态系统向前发展! 有如此多的支持null安全的软件包,您很有可能开始迁移应用程序以使用null安全。第一步是用来dart pub outdated检查您的应用程序的依赖关系。有关详细信息,请参见《空安全迁移指南》。我们还更改了dart createflutter create模板,以便现在默认情况下在新应用和程序包中启用null安全。

类型别名

类型别名是2.13语言的一项新功能。它扩展了我们先前的支持,该支持允许创建函数类型的类型别名,但不能创建任何其他类型。这个备受追捧的功能是语言问题跟踪器中评分排名第二的功能。 使用类型别名,可以为任何现有类型创建一个新名称,然后可以在可以使用原始类型的任何地方使用该名称。您实际上并没有定义新的类型,只是引入了简写别名。别名甚至通过类型相等性测试:

typedef Integer = int;
void main() {
  print(int == Integer); // true
}

那么,您可以将类型别名用于什么呢?一种常见的用法是为一种类型赋予一个简短或更具描述性的名称,从而使您的代码更具可读性和可维护性。 一个很好的示例是使用JSON。在这里,我们可以定义一个新的类型别名Json,该别名将JSON文档描述为从String键到任何值(使用该dynamic类型)的映射。然后,Json当定义fromJson命名构造函数和jsongetter时,可以使用该类型别名

typedef Json = Map<String, dynamic>;
class User {
  final String name;
  final int age;
  User.fromJson(Json json) :
    name = json['name'],
    age = json['age'];
Json get json => {
    'name': name,
    'age': age,
  };
}

您还可以在命名类的类型别名上调用构造函数,因此以下内容完全合法:

main(){ 
  var j = Json(); 
  j ['name'] ='Michael'; 
}

通过使用类型别名为复杂类型命名,可以使读者更容易理解代码的不变式。例如,以下代码定义类型别名,以描述包含通用类型的键和typeX值的映射List<X>。通过为类型赋予一个带有单个类型参数的名称,映射的规则结构对代码阅读者来说变得更加明显。

typedef MapToList<X> = Map<X, List<X>>;
void main() {
  MapToList<int> m = {};
  m[7] = [7]; // OK
  m[8] = [2, 2, 2]; // OK
  for (var x in m.keys) {
    print('$x --> ${m[x]}');
  }
}
=>
7 --> [7]
8 --> [2, 2, 2]

如果尝试使用不匹配的类型,则会收到分析错误:

m[42] = ['The', 'meaning', 'of', 'life'];
=>
The element type 'String' can't be assigned to the list type 'int'.

重命名公共库中的类时,甚至可以使用类型别名。想象一下PoorlyNamedClass,您想将公共库中现有的类重命名为BetterNamedClass。如果仅重命名该类,则您的API客户将突然获得编译错误。使用类型别名,您可以继续进行重命名,但是可以为旧的类名称定义一个新的类型别名,然后@Deprecated为该旧名称添加注释。使用时,PoorlyNamedClass会在使用时引起警告,但仍会像以前一样继续编译和工作,使用户有时间升级其代码。 以下是实现BetterNamedClass和弃用的方式PoorlyNamedClass(在名为的文件中mylibrary.dart):

class BetterNamedClass {...}
@Deprecated('Use BetterNamedClass instead')
typedef PoorlyNamedClass = BetterNamedClass;

这就是某人尝试使用的内容PoorlyNamedClass

import 'mylibrary.dart';
void main() {
  PoorlyNamedClass p;
}
=>
'PoorlyNamedClass' is deprecated and shouldn't be used. Use BetterNamedClass instead.

从Dart 2.13开始,可以使用类型别名功能。要启用它,请将pubspec.yaml中较低的Dart SDK约束设置为至少2.13

environment:
  sdk: ">=2.13.0 <3.0.0"

由于语言版本控制,此功能向后兼容。即使在2.13之前的程序包无法定义自己的类型别名,在2.13下具有较低SDK约束的程序包也可以安全地引用2.13程序包中定义的类型别名。

Dart 2.13 FFI变更

在Dart FFI中,我们还有一些新功能,这是我们用于调用C代码的互操作机制。 首先,FFI现在支持具有内联数组的结构。考虑一个具有内联数组的C结构,如下所示:

struct MyStruct { 
  uint8_t arr [8]; 
}

现在,您可以将其直接包装在Dart中,并使用类型参数指定元素类型Array

class StructInlineArray extends Struct {
  @Array(8)
  external Array<Uint8> arr;
}

其次,FFI现在支持打包结构。通常,将结构布置在内存中,以便成员位于地址边界内,以便于CPU访问。对于打包的结构,通常以特定于平台的方式省略了某些填充以降低总体内存消耗。使用新的@Packed(<alignment>)注释,您可以轻松指定填充。例如,以下代码创建一个在内存中具有4字节对齐的结构:

@Packed(4)
class TASKDIALOGCONFIG extends Struct {
  @Uint32()
  external int cbSize;
  @IntPtr()
  external int hwndParent;
  @IntPtr()
  external int hInstance;
  @Uint32()
  external int dwFlags;
  ...
}

Dart 2.13的性能变化

我们正在继续努力减少Dart代码的应用程序大小和内存占用。在大型Flutter应用程序中,表示AOT编译的Dart程序的元数据的内部结构可能会占用相当大的内存。提供这些元数据的大部分是为了启用诸如热重装,交互式调试以及人类可读堆栈跟踪的格式设置之类的功能,这些功能在已部署的应用程序中从未使用过。在过去的一年中,我们一直在重组Dart本机运行时,以消除尽可能多的此类开销。其中一些改进适用于所有以发行模式构建的Flutter应用程序,但有些改进要求您通过使用--split-debug-info标志将调试信息从AOT编译的应用程序中分离出来,从而放弃人类可读的堆栈跟踪。

Dart 2.13包含许多更改,这些更改大大减少了--split-debug-info使用时程序元数据所占用的空间。以Flutter Gallery应用程序为例。在Android上,发布的APK包含调试信息为112.4 MB,不包含调试信息为106.7 MB(减少了5%)。这个APK包含很多资产。仅查看APK中的代码元数据,它从Dart 2.12中的5.7MB减少到Dart 2.13中的3.7MB(减少了35%)

如果应用程序大小和内存占用对您很重要,请考虑使用该--split-debug-info标志省略调试信息。请注意,这样做时,您将需要使用symbolize命令使堆栈跟踪再次可被人类读取。

官方Docker支持和Google Cloud上的Dart

Dart现在可作为Docker Official Images使用。尽管Dart提供了Docker映像已有多年,但这些新的Dart映像已由Docker进行了测试和验证,以遵循最佳实践。它们还支持提前(AOT)编译,这可以大大减少已构建容器的大小,并可以提高在容器环境(如Cloud Run)中的部署速度。 尽管Dart一直致力于使诸如Flutter之类的应用程序框架能够在每个屏幕上驱动漂亮的像素,但我们意识到,大多数用户体验背后都是至少一项托管服务。通过使用Dart轻松构建后端服务,我们支持完整的堆栈体验,使开发人员可以使用与为前端小部件提供支持的语言和业务逻辑相同的语言和业务逻辑,将其应用程序扩展到云中。

通常,将Dart用于Flutter应用程序后端特别适合Google托管的无服务器平台Cloud Run的简单性和可伸缩性。这包括从零到零的比例,这意味着当后端不处理任何请求时,您不会招致费用。我们与Google Cloud团队合作,为Dart提供了Functions Framework,这是一组软件包,工具和示例,可以轻松编写Dart函数来部署,而不是使用完整的服务器来处理HTTP请求和CloudEvent。 查看我们的Google Cloud文档以开始使用。

后续计划

我们已经在为即将发布的版本进行一些激动人心的更改。与往常一样,您可以使用language funnel来关注我们的进度。

我们正在研究的一个领域是针对Dart和Flutter的一组新的规范。lints是配置Dart静态分析的强大方法,但是由于有数百种可能的lints可以打开或关闭,因此很难决定要选择什么。我们目前正在定义两个标准的lints集,默认情况下,我们将在Dart和Flutter项目中应用这些lints集。我们希望在下一个稳定版本中默认启用此功能。如果需要预览,请签出lintsflutter_lints这两个包。 最后,如果您要深度嵌入Dart VM运行时,请注意,我们打算为此弃用现有机制。我们将用基于Dart FFI的更快,更灵活的模型替换它。