Rust程式語言標準函式庫內建的「fmt」模組,提供了「Debug」和「Display」等特性,可以讓有實作這些特性的結構實體直接被使用在「format!」巨集以及其相關巨集中。在對結構體實作「Debug」和「Display」等特性時,我們需要透過「Formatter」結構實體提供的「write_str」、「write_char」或「write_fmt」這三個方法,來寫入要被組合進來的字串。如果我們要寫入的字串僅有一個字元,「write_str」方法與「write_char」方法,究竟哪個會比較快呢?



我們先來看看以下程式碼:

use std::fmt::{self, Display, Formatter};

struct A {}

impl Display for A {
    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        f.write_str("/")
    }
}

Formatter結構實體的「write_str」方法,實際上會再去呼叫其「buf」欄位值的「write_str」方法,至於這個「buf」欄位值是什麼,就得看在使用「format!」的相關巨集時,是把字串寫給哪個有實作「fmt」模組下的「Write」特性的結構實體。以「format!」這個巨集來說,就會是一個新產生出來的String結構實體;而以「write!」這個巨集來說,就會是透過其第一個參數傳進去的結構實體。

延續上面的程式碼,如果我們再繼續加上以下的程式碼:

fn main() {
    let s = format!("{}", A {});
}

那麼程式在執行的時候,因為是使用「format!」這個巨集的關係,就會先產生出一個String結構實體和一個Formatter結構實體,並將這個String結構實體的參考作為Formatter結構實體的「buf」欄位值。接著因為「format!」巨集的第一個參數是「{}」的關係,所以會去呼叫A結構實體「Display」特性的「fmt」方法。在這個「fmt」方法中,會去呼叫Formatter結構實體的「write_str」方法,也就是說,它又會再去呼叫其「buf」欄位值的「write_str」方法。而這個「buf」欄位值,就正是String結構實體的參考啦!所以我們要看一下String結構實體是怎麼實作這個「write_str」方法,程式碼如下:

impl fmt::Write for String {
    #[inline]
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.push_str(s);
        Ok(())
    }

    #[inline]
    fn write_char(&mut self, c: char) -> fmt::Result {
        self.push(c);
        Ok(())
    }
}

String結構體有實作「fmt」模組下的「Write」特性,可以看到其「write_str」方法會去呼叫String結構實體的「push_str」方法,「write_char」方法會去呼叫String結構實體的「push」方法。

如果去查看Formatter結構實體的「write_char」方法,會發現它本身並沒有提供公開的「write_char」方法,但它有去實作「fmt」模組下的「Write」特性。程式碼如下:

impl Write for Formatter<'_> {
    fn write_str(&mut self, s: &str) -> Result {
        self.buf.write_str(s)
    }

    fn write_char(&mut self, c: char) -> Result {
        self.buf.write_char(c)
    }

    fn write_fmt(&mut self, args: Arguments) -> Result {
        write(self.buf, args)
    }
}

至此我們可以了解到,Formatter結構實體的「write_str」方法,最終會去呼叫其「buf」欄位值的結構實體所實作的「write_str」方法。而Formatter結構實體的「write_char」方法,最終會去呼叫其「buf」欄位值的結構實體所實作的「write_char」方法。

使用「format!」巨集時,Formatter結構實體「buf」欄位值是String結構實體的參考,因此對於「write_str」方法與「write_char」方法,哪個會比較快的問題,其實就類似於String結構實體的「push_str」方法與「push」方法哪個比較快。根據之前的效能分析:

https://magiclen.org/rust-one-char-push-measurement

我們可以猜測出,當字元經過UTF-8編碼後的資料長度等於1時,Formatter結構實體的「write_char」方法效能會比「write_str」方法好。但是當字元經過UTF-8編碼後的資料長度大於1時,「write_str」方法的效能就會比「write_char」方法好。

實測看看?

為了驗證我們的猜測,就要實際寫一段程式來測試運算效能啦!這段程式可以在GitHub上取得:

https://github.com/magiclen/rust-performance-measurement/blob/master/benches/one_char_write.rs

根據測試結果,可以驗證我們的猜測是正確的。但這樣的結論只能用在Formatter結構實體的「buf」欄位值是String結構實體參考的狀況下,至於其它狀況就得看該「buf」欄位值的結構實體是如何去實作「fmt」模組下的「Write」特性。