如果要在 Dart 中使用像是 C 的 Sprintf 或是 Python formate 的功能,要怎麼做呢? 答案是,沒有內建的方法。說實在的,我很難相信現代程式語言居然沒有這個特性,那怎麼辦呢?本文蒐集了相關資料以及可能解法。

為什麼 Dart 不支援這個特性?

Dart 官方認為此類功能不應實作,而是交由第三方實作,讓開發者自己挑選偏好的 formate 方式。在 Dart 中 issue:1694Irhn 解釋了三個理由:

這邊來爬梳一下 Irhn 的想法。

理由一: Dart 沒有 var-args,然後 string interpolations 就夠用了。

在 Dart 中,由於 string interpolations 非常方便,很少需要 sprintf 風格的格式化。典型的 string interpolations 長這樣:

"Price: ${x.toStringAsFixed(3)} USD"

他認為可能比 sprintf 風格的格式化寫起來稍微長一些,但在實際應用中差別並不顯著。例如,"Price: ${x.toStringAsFixed(3)} USD""Price: %3d USD".format([x]) 在實際編寫時差異不大,而且在效率上遠高於格式化,更具類型安全性,並且可以自由地使用參數。

再來 Dart 缺乏可變參數(var-args),可變參數允許函數接受數量不定的參數,但 Dart 不支持這種特性,所以是語言本身設計如此。隨後,他又解釋動態字串格式化為何無法在 compile-time 達成。

理由二:動態字串格式化在 compile-time 無法實作

Irhn 解釋 Dart 是一種靜態類型語言,它在編譯時對類型和表達式進行嚴格檢查。這意味著,與 sprintf 允許的運行時字符串格式化和參數替換不同,Dart 需要在編譯時就明確知道字串的結構和類型。雖然 Dart 提供了一些內置的字串操作函數,但這些功能都需要在編譯時確定,無法完全模擬 sprintf 的動態和靈活性。

[!quote] I assume that "dynamic string formatting" means that the actual format string is not available at compile-time at all, and is provided as data at run-time. That means that you cannot create a function to do the formatting because that also requires the format to be known at compile-time. It is necessarily dynamically typed code since the requirements are not known at compile-time.

隨後 Irhn 又說,哎啊!你們自己用第三方套件就好啦,讓第三方套件自由競爭,開發者想用哪套就用哪套。

[!quote] To support dynamic formatting, I am not worried about relying on a package instead of having it in the platform libraries. That allows competing formatting packages to exist, without canonicalizing a single format. That's an advantage, because I don't actually think that there is a single format which is superior. It does mean that users need to make a choice (which formats to support, which package to use). That's no different from any other number of choices you need to do when picking frameworks for a larger application.

這條留言,接收了許多人的倒讚。說實在的,我很難相信現代程式語言居然沒有這個特性。 那怎麼辦呢?

理由三:可以由第三方套件解決,而且優點是不會限制開發者必須使用特定格式

如果使用的情境不太複雜,其實可以如此 gist展示的一樣,用 Regex 實作一個 sprintf 做字串替換。很輕量,套件相依複雜度跟執行檔大小相對小。我自己的專案多是用這套。

用法如下。

var template = "My name is {{name}} and I'm {{age}} years old"
sprintf(template, ['Oval', 17]);

如果這不夠用,那就要去 pub.dev 找找。簡陋的如 string_templates、templates_string;強大極度複雜的有 sprintfformate

sprintf

sprintf 是一個 Dart 語言的套件,提供了類似於 C 語言中 sprintf 函數的功能,使開發者能夠以靈活且格式化的方式處理字符串。這個套件支持多種格式化選項,包括數字、字符串和日期等,並允許精確控制輸出格式。

import 'package:sprintf/sprintf.dart';

void main() {
	print(sprintf("%04i", [-42]));
	print(sprintf("%s %s", ["Hello", "World"]));
	print(sprintf("%#04x", [10]));
}

formate

formate是一個用於 Dart 語言的字符串格式化套件,其功能類似於 Python 中的 format 方法。這個套件的靈感來自於 C++20 中的 std::format 函數,而 std::format 則是基於廣受歡迎的 C 語言函數 sprintf 的發展。其核心概念是,將用大括號 {} 包圍的模板替換為傳遞參數的值,並按需進行格式化。

用法

import 'package:format/format.dart';
import 'package:intl/intl.dart';

'{:0{},d}'.print(123, 9); // "0,000,123"
'{:0{},d}'.print(123, 11); // "000,000,123"
'{value:0{width},d}'.print({'value': 123, 'width': 13}); // "0,000,000,123"

結論

如果沒太複雜的需求,直接用 Regex 做一個小函式即可,當遇到更複雜的需求時,再使用 sprintf 或是 formate 套件。個人因為比較熟悉 Python 語法,所以偏好採用 formate, 其語法糖也比較精簡。