0%

【文章翻譯】Announcing Dart 2.12

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

宣佈 Dart 2.12

今天,我們宣佈推出 Dart 2.12,其中包含 完善的空安全Dart FFI 的穩定版本。空安全是我們最新的主要生產力功能,旨在幫助您避免空錯誤,這是一類通常難以發現的錯誤,如此影片介紹 中所述。FFI 是一種互操作性機制,可讓您調用以 C 語言編寫的現有程式碼,例如調用 Windows Win32 API。Dart 2.12 現已推出。

Dart 平台的獨特功能

在詳細探討完善的空安全和 FFI 之前,讓我們先討論一下它們如何符合我們對 Dart 平台的目標。程式語言往往具有許多共同的功能。例如,許多語言都支援物件導向程式設計或在網路上執行。真正讓語言與眾不同的是它們獨特的功能組合。

Dart 的獨特功能涵蓋三個維度:

  • 可攜性:高效的編譯器為設備生成 x86 和 ARM 機器碼,並為網路生成優化的 JavaScript。支援廣泛的目標平台 - 行動設備、桌上型電腦、應用程式後端等等。大量的函式庫和套件提供了可在所有平台上運作的一致 API,進一步降低了建立真正的跨平台應用程式的成本。
  • 生產力:Dart 平台支援熱重載,可為原生設備和網路實現快速、迭代的開發。Dart 還提供豐富的結構,例如 isolates 和 async/await,用於處理常見的並行和事件驅動的應用程式模式。
  • 穩健性:Dart 完善的空安全類型系統可在開發過程中捕獲錯誤。整個平台具有高度的可擴展性和可靠性,大型應用程式(包括 Google Ads 和 Google Assistant 等業務關鍵型應用程式)已在生產環境中使用超過十年。

完善的空安全使類型系統更加穩健,並提升效能。Dart FFI 可讓您使用現有的 C 函式庫以獲得更好的可攜性,並讓您可以選擇使用高度調整的 C 程式碼來執行效能關鍵型任務。

完善的空安全

完善的空安全是自 Dart 2.0 中引入完善的類型系統以來,對 Dart 語言的最大補充。空安全進一步增強了類型系統,使您能夠捕獲空錯誤,這是應用程式崩潰的常見原因。透過選擇使用空安全,您可以在開發過程中捕獲空錯誤,從而防止生產環境中的崩潰。

完善的空安全是圍繞幾個核心原則設計的。讓我們重新審視這些原則如何影響您作為開發人員。

預設不可为空:類型系統的根本性變化

在空安全之前,核心挑戰是您無法區分預期傳遞空值的程式碼和不適用於空值的程式碼。幾個月前,我們在 Flutter master channel 中發現了一個錯誤,其中各種 flutter tool 指令會在某些機器配置上因空錯誤而崩潰:在 null 上調用了方法 ‘>=’。根本原因是如下程式碼:

1
2
3
4
5
6
final int major = version?.major;
final int minor = version?.minor;
if (globals.platform.isMacOS) {
// Android Studio 的 plugin 路徑在 4.1 版後發生了變化。
if (major >= 4 && minor >= 1) {
...

您能發現錯誤嗎?因為 version 可以為 null,所以 major 和 minor 也都可能為 null。這個錯誤在這裡單獨看來似乎很容易發現,但在實踐中,這樣的程式碼經常會被忽略,即使是像 Flutter 儲存庫中使用的嚴格程式碼審查流程也是如此。有了空安全,靜態分析可以立即捕獲此問題。(在 DartPad 中即時試用。)

IDE 中分析輸出的螢幕截圖

這是一個非常簡單的錯誤。在 Google 內部早期使用空安全的程式碼中,我們發現了更多複雜的錯誤。其中一些錯誤已經存在多年了,但如果沒有空安全的額外靜態檢查,團隊就無法找到原因。以下是一些例子:

  • 一個內部團隊發現,他們經常檢查永遠不可能為 null 的表達式的空值。這個問題最常見於使用 protobuf 的程式碼中,其中可選欄位在未設定時返回預設值,而不是 null。因此,程式碼會因為混淆預設值和空值而錯誤地檢查預設條件。
  • Google Pay 團隊在他們的 Flutter 程式碼中發現了錯誤,當他們嘗試在 Widget 上下文之外存取 Flutter State 物件時,這些程式碼會失敗。在空安全之前,這些物件會返回 null 並掩蓋錯誤;有了空安全,完善的分析確定這些屬性永遠不可能為 null,並拋出分析錯誤。
  • Flutter 團隊發現了一個錯誤,如果將 null 傳遞給 Window.render() 中的 scene 參數,Flutter 引擎可能會崩潰。在空安全遷移過程中,他們加入了一個提示,將 Scene 標記為不可为空,然後就能夠輕鬆防止 null 可能觸發的潛在應用程式崩潰。

使用預設不可为空

啟用空安全後,變數宣告的基礎知識會發生變化,因為預設類型是不可为空的:

1
2
3
4
// 在空安全 Dart 中,這些都不可能為 null。
var i = 42; // 推斷為 int。
String name = getFileName();
final b = Foo();

如果要建立一個可以包含值或 null 的變數,則需要透過在類型後面加上 ? 後綴,在變數宣告中明確指出:

1
2
// aNullableInt 可以包含整數或 null。
int? aNullableInt = null;

空安全的實作非常穩健,具有豐富的靜態流程分析,使使用可为空類型更加容易。例如,在檢查 null 後,Dart 將局部變數的類型從可为空提升為不可为空:

1
2
3
4
5
6
7
int definitelyInt(int? aNullableInt) {
if (aNullableInt == null) {
return 0;
}
// aNullableInt 現在已提升為不可为空的 int。
return aNullableInt;
}

我們還加入了一個新的關鍵字 required。當命名參數標記為 required(這在 Flutter Widget API 中經常發生)並且調用者忘記提供參數時,就會發生分析錯誤:

逐步遷移至空安全

由於空安全是對我們類型系統的根本性變更,如果我們堅持強制採用,將會造成極大的破壞。因此, 可以決定何時是合適的時機,空安全是一項可選功能:您可以使用 Dart 2.12 而無需強制啟用空安全。您甚至可以依賴已啟用空安全的套件,無論您的應用程式或套件是否已啟用空安全。

為了幫助您將現有程式碼遷移至空安全,我們提供了一個遷移工具和一個遷移指南。該工具首先分析您所有的現有程式碼。然後,您可以互動式地審查該工具推斷出的可空性屬性。如果您不同意該工具的任何結論,您可以加入可空性提示來更改推斷。加入一些遷移提示可以對遷移品質產生巨大影響。

目前,使用 dart createflutter create 建立的新套件和應用程式不會啟用完善的空安全。我們預計在未來的穩定版本中更改這一點,屆時我們會看到大多數生態系統都已遷移。您可以使用 dart migrate 輕鬆地在新建的套件或應用程式中啟用空安全

Dart 生態系統空安全遷移狀態

在過去的一年中,我們提供了幾個完善的空安全的預覽版和測試版,目的是在生態系統中播種支援空安全的套件。這項準備工作很重要,因為我們建議按順序遷移至完善的空安全 - 在所有依賴項都已遷移之前,您不應遷移套件或應用程式。

我們已經發佈了由 DartFlutterFirebaseMaterial 團隊提供的數百個套件的空安全版本。我們也看到了來自 Dart 和 Flutter 生態系統的大力支援,因此 pub.dev 現在有超過一千個套件支援空安全。重要的是,最受歡迎的套件率先遷移,因此在前 100 個最受歡迎的套件中,有 98% 支援空安全,前 250 個中有 78%,前 500 個中有 57% 在今天的發佈之前已經支援空安全。我們期待在未來幾周內看到更多具有空安全的套件出現在 pub.dev 上。我們的分析顯示,pub.dev 上的絕大多數套件已經沒有阻礙,可以開始遷移

完善的空安全的好處

一旦您完全遷移,Dart 的空安全就是完善的。這意味著 Dart 可以 100% 確定具有不可为空類型的表達式不可能為 null。當 Dart 分析您的程式碼並確定變數為不可为空時,該變數始終 為不可为空。Dart 與 Swift 共享完善的空安全,但其他程式語言並不多。

Dart 空安全的完善性還有另一個值得歡迎的含義:它意味著您的程式可以更小、更快。因為 Dart 確定不可为空變數永遠不會為 null,所以 Dart 可以優化。例如,Dart ahead-of-time (AOT) 編譯器可以生成更小、更快的原生程式碼,因為它在知道變數不為 null 時不需要加入 null 檢查。

Dart FFI,用於整合 Dart 與 C 函式庫

Dart FFI 可讓您利用 C 函式庫中的現有程式碼,以提高可攜性,並與高度調整的 C 程式碼整合,以執行效能關鍵型任務。從 Dart 2.12 開始,Dart FFI 已脫離 測試 階段,現在被認為是穩定的,可供生產使用。我們還加入了一些新功能,包括巢狀結構體和按值傳遞結構體。

按值傳遞結構體

在 C 程式碼中,結構體可以透過引用和按值傳遞。FFI 以前只支援透過引用傳遞,但從 Dart 2.12 開始,您可以按值傳遞結構體。以下是一個透過引用和按值傳遞的兩個 C 函數的小例子:

1
2
3
4
struct Link {
double value;
Link* next;
};
1
2
3
void MoveByReference(Link* link) {
link->value = link->value + 10.0;
}
1
2
3
4
Coord MoveByValue(Link link) {
link.value = link.value + 10.0;
return link;
}

巢狀結構體

C API 經常使用巢狀結構體 - 本身包含結構體的結構體,例如:

1
2
3
4
5
6
7
8
9
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 中加入了新的便捷函數,因此在常見情況下不需要額外的樣板程式碼來分配和釋放記憶體:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 分配一個指向 Utf8 陣列的指標,從 Dart 字串填充它,
// 將其傳遞給 C 函數,轉換結果,並釋放參數。
//
// 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 包裝器。我們邀請您試用:package:ffigen

FFI 路線圖

隨著核心 FFI 平台的完成,我們將把重點轉向透過在核心平台之上分層的功能來擴展 FFI 功能集。我們正在研究的一些功能包括:

FFI 的應用範例

我們已經看到許多創造性地使用 Dart FFI 與各種基於 C 的 API 整合的例子。以下是一些例子:

  • open_file 是一個用於在多個平台上打開檔案的單一 API。它使用 FFI 在 Windows、macOS 和 Linux 上調用原生作業系統 API。
  • win32 包裝了最常見的 Win32 API,可以直接從 Dart 調用各種 Windows API。
  • objectbox 是一個由基於 C 的實作支援的快速資料庫。
  • tflite_flutter 使用 FFI 包裝 TensorFlow Lite API。

Dart 語言的下一步是什麼?

完善的空安全是我們多年來對 Dart 語言所做的最大變更。接下來,我們將在牢固的基礎上,對語言和平台進行更多漸進式變更。以下是我們在語言設計漏斗中正在試驗的一些內容的快速概覽:

類型別名#65):建立非函數類型類型別名的能力。例如,您可以建立一個類型定義並將其用作變數類型:

1
2
typedef IntList = List<int>;
IntList il = [1,2,3];

三移位運算子#120):加入一個新的、完全可覆蓋的 >>> 運算子,用於對整數執行無符號位移。

泛型中繼資料註釋#1297):擴展中繼資料註釋以支援包含類型參數的註釋。

靜態元程式設計#1482):支援靜態元程式設計 - 在編譯期間產生新的 Dart 原始程式碼的 Dart 程式,類似於 Rust 巨集 和 Swift 函數建置器。此功能仍處於早期探索階段,但我們認為它可以實現當前依賴程式碼生成的使用案例。

Dart 2.12 現已推出

具有完善空安全和穩定 FFI 的 Dart 2.12 現已在 Dart 2.12Flutter 2.0 SDK 中提供。請花點時間查看Dart 的已知空安全問題Flutter 的已知空安全問題。如果您發現任何其他問題,請在 Dart 問題追蹤器 中回報。

如果您開發了在 pub.dev 上發佈的套件,請立即查看遷移指南,並了解如何遷移至完善的空安全。遷移您的套件可能有助於解除其他依賴它的套件和應用程式的阻礙。我們也要感謝那些已經遷移的人!

我們很樂意聽到您對完善的空安全和 FFI 的體驗。請在下方留言或在 Twitter 上 dart_lang 中提及我們。


宣佈 Dart 2.12 最初發佈於 Medium 上的 Dart,人們在那裡透過突出顯示和回應這個故事來繼續討論。