在去年春節,Flutter 官方發布了宏(Macros)編程的原型支持, 同年的 5 月份在 Google I/O 發布的 Dart 3.4 宣布了宏的實驗性支持,但是對于 Dart 內部來說,從啟動宏編程實驗開始已經過去了幾年,但是從目前的推進趨勢看,完全的宏功能支持并不理想,結論大概是:
能用是能用,但是質量和性能都達不到一開始的預期。
具體原來在于 Dart 的靜態語言提前編譯和有狀態的熱重載等方面,對于元編程而言,需要建立在強大的內省基礎支持之上,但是對于 Dart 目前來說,運行時內省(例如反射)會讓 tree-shaking 優化變得困難 ,而 tree-shaking 優化是 Dart 在二進制優化的重要指標之一。
一開始 Dart 的目標是構建一個完整的宏系統,從而讓該系統支持在編譯時對程序進行深度語義內省,但是在實現語義內省時引入了大量的編譯時成本,而這讓有狀態的熱重載保持變得困難。
目前的宏編程還讓 Flutter 開發時的 IDE 編輯與補全體驗下降。
同時帶來的還有依賴項里的宏循環依賴等問題,例如在 IDE 中輸入“.foo” 可能需要重新處理所有宏,從而執行正確的代碼,目前來看要么處理得太頻繁,要么給出的結果不正確。
在過去的測試里,宏在小型庫上的性能非常好,但是在真實應用的大周期開發里,會讓 Dart 的體驗變得很差,例如在頂層編輯(聲明、方法頭、字段等)時,基本上每次鍵入都需要重新運行整個宏構建。
而針對當前宏支持采用緩存的提議,也存在宏生成的代碼的整個版本適配問題,例如:
現在有一個依賴于 foo 和 bar 的 my_app 包,如果你只在 foo 上運行 pub get,解析器可能會給你 bar 1.2.3;而當你在 my_app上運行 pub get 時,也許會得到 bar 2.3.4,大概可能是 @doStuff 宏內省的 type from bar 在這些版本之間不同。
雖然也可以通過限制內省來避免這種深層依賴,但帶來的一些其他負面,例如你可能正在為 foo 生成 JSON 序列化代碼,并且宏正在嘗試判斷其類型來自 bar 的字段是否支持 JSON 序列化,甚至前面提到的循環依賴問題。
當然針對和這個可能還有其他解決方案,相比較目前帶來的編譯時間、靜態分析和整個程序的優化問題,對于 Dart 來說運行時方法并不現實。
所以最終 Dart 團隊決定,由于宏的性能具體目標還太遙遠,團隊決定把當前的實現回歸到編輯(例如靜態分析和代碼完成)和增量編譯(熱重載的第一步)上。
具體在于重新投資Dart 中的數據支持**,因為這也是Dart & Flutter issue 里請求最多的問題,事實上一開始 Dart 對宏支持的主要動機也是提供更好的數據序列化和反序列化,但是目前看來,通過更多定制語言功能來實現這一點更加實際。
另外通過縮短構建時間和整體代碼生成體驗來彌補宏的確實,也是未來目標之一,目前 Dart 已經確定了 build_runner 的改進支持。
另外還計劃提供 augmentations 功能,這是作為宏的一部分制作原型的功能,例如增加修飾符 augment
作為擴充聲明,而該功能也是獨立的部份,并將改進現有的代碼生成。
通過 augment 實現將一個功能部署到多個文件里,同時可以添加新的頂級聲明,將新成員注入類,并將函數和變量包裝在其他代碼中。
相信宏支持停止這個消息會讓大家感到失望,盡管從長遠來看 Dart 仍然對通用元編程感興趣,因為它在數據之外還有許多潛在的用例,但是在短期之內,Dart 應該是不會發布宏支持。
對于包開發者來說,比如之前的 equatable 在 3.0.0-dev.1
就發布過宏的實驗性版本,體驗還不錯,但是現在看來只能繼續“實驗”下去。
最后,祝大家 2025 新春快樂~
參考鏈接
- https://medium.com/dartlang/an-update-on-dart-macros-data-serialization-06d3037d4f12
- https://github.com/dart-lang/build/issues/3800