0%

【文章翻譯】Announcing Dart 2.13

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

宣佈 Dart 2.13

A graphic showing highlights of what’s in this post: type aliases, better FFI, null safety, Docker support

作者:Kevin Moore 和 Michael Thomsen

今天,我們宣佈推出 Dart 2.13,其中包含類型別名——目前我們第二個最受歡迎的語言功能。Dart 2.13 還包含改進的 Dart FFI 和更好的效能,並且我們有新的 Dart 官方 Docker 映像。這篇文章提供了在 2.12 中引入的空安全功能的更新,討論了新的 2.13 功能,有一些關於 Docker 和 Google Cloud 支援 Dart 後端的令人興奮的消息,並預覽了一些您可以在未來版本中看到的一些變更。

空安全更新

我們在 3 月的 Dart 2.12 版本中推出了健全的空安全。空安全是 Dart 最新的主要生產力功能,旨在幫助您避免空錯誤——這類錯誤通常難以發現。隨著該版本的推出,我們鼓勵套件發佈者開始將 pub.dev 上的共享套件遷移到空安全。

我們非常高興地看到空安全被採用的速度如此之快!在發佈後的短短幾個月內,pub.dev 上前 500 個最受歡迎的套件中,有 93% 已經支援空安全。我們要向所有套件開發人員表示衷心的感謝,感謝他們如此迅速地完成了這項工作,並幫助整個生態系統向前發展!

有了這麼多支援空安全的套件,您很有可能可以開始將您的應用程式遷移到使用空安全。第一步是使用 dart pub outdated 檢查您的應用程式的相依性。有關詳細資訊,請參閱空安全遷移指南。我們還更改了我們的 dart createflutter create 範本,以便它們現在預設在新應用程式和套件中啟用空安全。

宣佈類型別名

類型別名是 2.13 語言中的一項新功能。它擴展了我們早期的支援,允許建立函數類型的類型別名,但不支援任何其他類型。這個備受期待的功能在語言問題追蹤器中被評為第二高

使用類型別名,您可以為任何現有類型建立新名稱,然後可以在任何可以使用原始類型的地方使用該名稱。您並不是真的在定義一個新類型,只是引入了一個簡寫別名。別名甚至通過類型相等測試:

1
2
3
4
5
typedef Integer = int;

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

那麼您可以使用類型別名做什麼呢?一種常見的用法是為類型指定一個更短或更具描述性的名稱,使您的程式碼更具可讀性和可維護性。

一個很好的例子是使用 JSON(感謝 GitHub 使用者 Levi-Lesches 提供的這個例子)。在這裡,我們可以定義一個新的類型別名 Json,它將 JSON 文件描述為從 String 鍵到任何值(使用動態類型)的映射。然後,我們可以在定義我們的 fromJson 命名建構函數和 json getter 時使用該 Json 類型別名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,
};
}

您也可以在命名類別的類型別名上調用建構函數,因此以下是完全合法的:

1
2
3
4
main() {
var j = Json();
j['name'] = 'Michael';
}

透過使用類型別名為複雜類型命名,您可以使讀者更容易理解程式碼的不變性。例如,以下程式碼定義了一個類型別名來描述包含泛型類型 X 的鍵和 List<X> 類型值的映射。透過為類型指定一個帶有單個類型參數的名稱,映射的規則結構對於程式碼的讀者來說變得更加明顯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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]

如果您嘗試使用不匹配的類型,您將收到分析錯誤:

1
2
3
4
5
m[42] = ['The', 'meaning', 'of', 'life'];

=>

元素類型 'String' 無法賦值給列表類型 'int'

您甚至可以在重命名公共程式庫中的類別時使用類型別名。想像一下,您在公共程式庫中有一個現有的類別 PoorlyNamedClass,您想將其重命名為 BetterNamedClass。如果您只是重命名類別,那麼您的 API 客戶將會收到突然的編譯錯誤。使用類型別名,您可以繼續進行重命名,但然後為舊的類別名稱定義一個新的類型別名,然後為舊名稱添加一個 @Deprecated 註釋。使用 PoorlyNamedClass 時會導致警告,但會繼續編譯並像以前一樣工作,讓使用者有時間升級他們的程式碼。

以下是您如何在 mylibrary.dart 檔案中實作 BetterNamedClass 並棄用 PoorlyNamedClass

1
2
3
4
class BetterNamedClass {...}

@Deprecated('請改用 BetterNamedClass')
typedef PoorlyNamedClass = BetterNamedClass;

以下是當有人嘗試使用 PoorlyNamedClass 時會發生的情況:

1
2
3
4
5
6
7
8
9
import 'mylibrary.dart';

void main() {
PoorlyNamedClass p;
}

=>

'PoorlyNamedClass' 已棄用,不應使用。請改用 BetterNamedClass。

類型別名功能從 Dart 2.13 開始提供。要啟用它,請將 pubspec 中的 Dart SDK 下限約束設定為至少 2.13:

1
2
environment:
sdk: ">=2.13.0 <3.0.0"

由於語言版本控制,此功能向後相容。SDK 約束低於 2.13 的套件可以安全地引用在 2.13 套件中定義的類型別名,即使 2.13 之前的套件無法定義自己的類型別名。

Dart 2.13 FFI 變更

我們在 Dart FFI(我們用於調用 C 程式碼的互操作機制)中也有一些新功能。

首先,FFI 現在支援具有內聯陣列的結構體(#35763)。考慮一個具有內聯陣列的 C 結構體,如下所示:

1
2
3
struct MyStruct {
uint8_t arr[8];
}

您現在可以直接在 Dart 中包裝它,使用 Array 的類型參數指定元素類型:

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

其次,FFI 現在支援壓縮結構體(#38158)。通常,結構體在記憶體中的佈局方式使得成員位於更容易被 CPU 存取的地址邊界中。使用壓縮結構體,可以省略一些填充以降低整體記憶體消耗,通常以特定於平台的方式。使用新的 @Packed(<alignment>) 註釋,您可以輕鬆指定填充。例如,以下程式碼建立了一個在記憶體中具有 4 位元組對齊的結構體:

1
2
3
4
5
6
7
8
9
10
11
12
@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.7 MB 減少到 Dart 2.13 的 3.7 MB(減少了 35%)。

如果應用程式大小和記憶體佔用對您很重要,請考慮使用 --split-debug-info 旗標省略除錯資訊。請注意,這樣做的話,您需要使用符號化命令使堆疊追蹤再次可讀。

官方 Docker 支援和 Google Cloud 上的 Dart

Dart 現在可以作為Docker 官方映像使用。雖然 Dart 多年來一直提供 Docker 映像,但這些新的 Dart 映像已通過 Docker 的測試和驗證,符合最佳實務。它們還支援提前 (AOT) 編譯,這可以顯著減少構建的容器的大小,並可以提高在容器環境(例如Cloud Run)中的部署速度。

雖然 Dart 仍然專注於讓 Flutter 等應用程式框架能夠在每個螢幕上驅動漂亮的像素,但我們意識到,大多數使用者體驗背後至少有一個託管服務。透過簡化使用 Dart 構建後端服務,我們支援完整的堆疊體驗,讓開發人員可以使用與他們用於在前段驅動 widget 的相同語言和業務邏輯將他們的應用程式擴展到雲端。

一般來說,將 Dart 用於 Flutter 應用程式後端尤其適合 Google 的託管無伺服器平台 Cloud Run 的簡潔性和可擴展性。這包括縮放為零,這意味著當您的後端沒有處理任何請求時,您不會產生成本。我們與 Google Cloud 團隊合作,提供Dart 函數框架,這是一個套件、工具和範例的集合,可以輕鬆編寫 Dart 函數來部署,而不是完整的伺服器來處理 HTTP 請求和 CloudEvents。

請查看我們的Google Cloud 文件以開始使用。

關於接下來的一些話

我們已經在為即將發佈的版本進行一些令人興奮的變更。與往常一樣,您可以使用語言漏斗追蹤器來關注我們的進度。

我們正在研究的一個領域是 Dart 和 Flutter 的一套新的規範 lint。Lint 是一種配置 Dart 靜態分析的強大方法,但由於有數百個可能的 lint 可以開啟或關閉,因此很難決定選擇什麼。我們目前正在定義兩套規範的 lint,我們將在 Dart 和 Flutter 專案中預設應用這些 lint。我們預計這將在下一個穩定版本中預設啟用。如果您想要預覽,請查看兩個套件 lintsflutter_lints

最後,如果您進行 Dart VM 運行時的深度嵌入,請注意,我們計劃棄用現有的機制。我們將用一個基於 Dart FFI 的更快、更靈活的模型來替換它(請參閱追蹤問題 #45451)。

Dart 2.13 現已推出

Dart 2.13,包含類型別名和改進的 FFI,現已在 Dart 2.13Flutter 2.2 SDK 中推出。

如果您一直在等待您的相依性遷移到空安全,您可能需要再次檢查,使用 dart pub outdated。前 500 個最受歡迎的套件中,有 93% 已經遷移,您很有可能已經沒有障礙了。我們還要向已經遷移的開發人員表示衷心的感謝!

我們很樂意聽到您對本部落格文章中討論的新功能和變更的體驗。請在下方留言或在 Twitter 上聯繫我們 @dart_lang


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