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 體驗,咱們評論區見!

相關文章
相關標籤/搜索