今天 Dart 2.12 釋出了,該版本具有穩定的空安全宣告和Dart FFI版本。
空安全是最新的關鍵生產力功能,旨在幫助開發者避免空錯誤,這是一項通常很難被發現的錯誤。
FFI是一種互操作性機制,可以讓開發者呼叫 C 語言編寫的現有程式碼,例如呼叫 Windows Win32 API。
Dart平臺的獨特功能
在詳細解釋空安全宣告和 FFI 之前,讓我們先討論一下 Dart 平臺如何將它適合我們的目標平臺上。
程式語言通常傾向於共享許多功能,例如許多語言支援物件導向的程式設計或在在 Web 上執行,真正使語言與眾不同的是它們獨特的功能組合。
Dart的獨特功能涵蓋了三個方面:
-
可移植性:高效的編譯器為裝置生成 x86 和 ARM 機器程式碼,併為 Web 生成優化後的JavaScript程式碼。所以 Dart 的持續目的就是支援:移動裝置、桌上型電腦、應用後端等等。大量的庫和軟體包提供了可在所有平臺上使用的一致性 API,從而進一步降低了建立真正的多平臺應用程式的成本。
-
高效:Dart平臺支援熱重灌,從而可以對本機裝置和Web進行快速地迭代和開發。Dart提供了豐富的結構,如
isolates
和async/await
,用於處理常見的併發和事件驅動的模式。 -
健壯:Dart 是健全的,null 安全系統會在開發過程中捕獲錯誤。整個平臺具有高度的可擴充套件性和可靠性,Dart 已經有十多年的歷史被運用於生產開發,其中包括 Google Ads和Google Assistant等業務關鍵型應用程式。
PS:事實上被大規模應用還是因為近幾年的 Flutter
可靠的空安全宣告使型別系統更加強大,並具更好的效能,而 Dart FFI 可以讓開發者用現有的 C 庫來得到了更便捷的可移植性,可以選擇對效能要求很高的任務使用經過C程式碼來排程實現。
空安全宣告
自 Dart 2.0 引入空安全宣告以來,空安全宣告是 Dart 語言的最大補充。空安全性進一步增強了型別系統,使得開發者能夠在開發階段就捕獲到空錯誤,這是過去應用程式崩潰的常見原因。
合理的空安全宣告是圍繞一些核心原則設計的,接下來讓我們瞭解下這對開發人員會有宣告影響。
預設情況下不可為空
空安全宣告之前的核心挑戰是,開發者無法分辨傳遞空值的程式碼與不能使用空值的程式碼之間的區別。
幾個月前,我們在 Flutter master channel 中發現了一個錯誤,該錯誤會在某些機器配置上會使得各種 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
可以為 null
,所以 major
和 minor
也可以為 null。
這樣的獨立錯誤看起來很容易被發現,但實際上即使是經過嚴格的程式碼審查過程(如Flutter Code Review),這樣的程式碼也始終無處不在。所以出於安全考慮,靜態分析會立即捕獲此問題。
那是一個非常簡單的錯誤,而在 Google 的內部,早期使用 null 安全性的過程中,我們發現了很多複雜的錯誤,其中一些是已經存在多年的 bug,但是如果沒有 null 安全性的額外靜態檢查,團隊就無法找到原因。
這裡有一些例子:
-
一個內部團隊發現,他們經常檢查到永遠不能為 null 的表示式得到了 null 值。使用protobuf 的程式碼中最經常出現此問題,其中可選欄位在未設定時返回預設值,並且永遠不會為null。如此一來,通過混淆預設值和空值,程式碼錯誤地檢查了預設條件。
-
Google Pay 小組在 Flutter 程式碼中發現了一些錯誤,這些錯誤會在
State
在上下文之外嘗試訪問 Flutter 物件的Widget
。在實現 null 安全之前,這些物件將返回 null並掩蓋錯誤;出於安全考慮,宣告分析確定這些屬性永遠不會為空,並引發了分析錯誤。 -
Flutter 小組發現了一個錯誤,如果將
null
的scene
引數傳遞給Window.render()
,Flutter 引擎可能會崩潰。在進行 null 安全遷移期間,他們新增了一個提示,將Scene
標記為non-nullable
,然後能夠輕鬆地防止可能觸發 null 的潛在應用崩潰。
預設情況下使用非空
一旦啟用空安全,變數宣告的基本行為會被改變,因為預設的型別是不可為空:
// 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();
複製程式碼
如果要建立一個可以包含值或 null 的變數,則需要通過 ? 在型別宣告中新增字尾來使該變數在變數宣告中顯式顯示:
// 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小部件API中經常發生)並且呼叫者忘記提供引數時,就會發生分析錯誤:
逐步遷移到空安全性
因為空安全性改變了我們的編碼習慣,所以如果堅持強制採用,那將是極度破壞性的。所以我們決定讓開發者在最需要的時候啟用,所以空安全是一項可選功能:你可以用 Dart 2.12 而無需被迫啟用空安全,你甚至可以依賴已經啟用了空安全性的軟體包,無論應用程式或軟體包是否啟用了空安全性。
為了幫助開發者將現有程式碼遷移到安全性狀態,我們提供了遷移工具和遷移指南。這些工具首先將分析所有現有程式碼,然後開發者可以互動地檢視該工具推斷的可空性屬性。
如果開發者不同意該工具的結論,則可以通過新增可空性提示以更改推斷,新增一些遷移提示可能會對遷移的質量帶來較大的影響。
目前使用 dart create
和 flutter create
不啟用空安全性宣告建立的新程式包和應用程式。使用 dart migrate
可以簡單地啟用空安全的功能。
Dart 生態系統的空安全遷移狀況
在過去的一年中,我們提供了幾種空安全宣告的預覽版和 Beta 版,目的是為生態系統植入支援空安全的軟體包。
這項準備工作很重要,因為我們建議按順序遷移,以確保空安全宣告不會影響開發者現有的應用,開發者在所以依賴完成遷移之前最好不要啟動空安全配置。
Dart,Flutter,Firebase 和 Material 團隊已經發布提供的數百個具備 null 安全的軟體包的版本,而且我們已經從驚人的 Dart 和 Flutter 生態系統中獲得了巨大的支援,因此pub.dev 現在有超過一千個支援 null 安全的軟體包。
重要的是,最受歡迎的那些軟體包已首先完成遷移,因此對於今天的釋出而言,最流行的前100個軟體包中有98%是支援 null safety,前250個頂級軟體包中的 78% 和前500個頂級軟體包中的 57% 也已經支援零安全性。
我們期待在未來幾周內在 pub.dev 上看到更多具有空安全性的軟體包。分析表明,pub.dev 上的絕大多數軟體包已被解除阻止,可以開始遷移。
完全空安全性的好處
完全遷移後,Dart 的空安全性就可以啟用了,這意味著 Dart 100% 確保具有不可為 null 的型別的表示式不能為 null 。
當 Dart 分析開發者的程式碼並確定某個變數不可為空時,該變數將始終為不可為空,而 Dart 與 Swift 共享空安全宣告,這在其他程式語言上很少見。
PS :Kotlin 有話要說
Dart 空安全宣告的健壯性性還具有另一個意義:這意味著您的程式可以更小,更快。
由於 Dart 確保不可為空的變數永遠不會為 null ,因此 Dart 可以進行優化。例如 Dart 提前(AOT)編譯器可以生成更小,更快的本機程式碼,因為當知道變數不為 null 時,它不需要新增對 null 的檢查。
Dart FFI,用於將Dart與C整合
Dart FFI 讓開發者能夠利用 C 語言中的現有程式碼,以實現更好的可移植性,並且利用調整的 C 程式碼整合以執行對效能要求較高的任務。
從Dart 2.12 開始,Dart FFI 已脫離Beta階段,現已被認為穩定並且可以投入生產,我們還新增了一些新功能,包括巢狀結構和按值傳遞結構。
通過值傳遞結構
可以在C程式碼中按引用和按值傳遞結構,FFI 以前僅支援按引用傳遞,但從 Dart 2.12 開始開發者可以按值傳遞結構,例如:
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
已新增了新的便利功能,所以在常見情況下,不需要分配和釋放記憶體。
// 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功能上,使其具有在核心平臺之上分層的功能,我們正在調查的一些功能包括:
- 特定於ABI的資料型別,例如int,long,size_t #36140
- 內聯結構中的陣列 #35763
- 打包的結構 #38158
- 聯合型別 #38491
- 將 finalizers 暴露給 Dart#35770
FFI的示例用法
前面我們已經講了 Dart FFI 的許多創造性用法,以與各種基於 C 的API整合,這裡有一些例子:
-
open_file 是用於跨多個平臺開啟檔案的單個API,它使用 FFI 來呼叫 Windows,macOS 和Linux上的本機作業系統API。https://pub.dev/packages/open_file
-
win32 封裝了最常見的Win32 API,從而可以直接從Dart呼叫各種Windows API。https://pub.dev/packages/win32
-
objectbox 是由基於C的實現支援的快速資料庫。https://pub.dev/packages/objectbox
-
tflite_flutter 使用FFI包裝TensorFlow Lite API。
Dart語言的下一步是什麼?
空安全宣告是我們幾年來對 Dart 語言所做的最大改變,接下來我們將考慮在我們強大的基礎上對語言和平臺進行更多的增量更改。
我們在語言設計渠道中正在嘗試的一些事情:
- Type aliases #65:可以為非函式型別建立型別別名,例如可以建立一個
typedef
並將其用作變數型別:
typedef IntList = List <int>;
IntList il = [1,2,3];
複製程式碼
-
Type aliases #120:新增了一個新的,完全可重寫的
>>>
運算子,用於對整數進行無符號移位。 -
Generic metadata annotations #1297:擴充套件後設資料註釋以也支援包含型別引數的註釋。
-
Static meta-programming #1482:支援靜態超程式設計 — Dart程式在編譯過程中會生成新的Dart 原始碼,類似於 Rust 巨集和 Swift 函式生成器(該功能仍處於早期探索階段,但是我們認為它可以啟用當今依賴於程式碼生成的用例。)
Dart 2.12 is available now
Dart 2.12 和 Flutter 2.0 SDK 現已提供具有可靠的空安全性和穩定FFI的,所以請花點時間檢視 Dart 和 Flutter 的已知的無效安全問題,如果你發現任何其他問題,請在 Dart tracker 中報告這些問題。
如果你已經在pub.dev上釋出了軟體包,請立即檢視遷移指南,並瞭解如何遷移以達到安全性。遷移軟體包可能會幫助解除阻止其他依賴於該軟體包的軟體包和應用程式,並且我們還要感謝已經遷移的人!