前回はフォーマット出力系のマクロの定義と呼び出している関数を確認した。 今回はそれらが実際に呼び出している core::fmt::write() の動作を見てみる。

Arguments

core::fmt::Arguments の定義は次のようになっている。

src/libcore/fmt/mod.rs

pub struct Arguments<'a> {
    pieces: &'a [&'a str],
    args: &'a [ArgumentV1<'a>],
    fmt: Option<&'a [rt::v1::Argument]>,
}

pieces はプレースホルダー("{}")の前後に挿入される文字列のスライス、args は 出力の対象となる変数への(型消去された)参照が格納された ArgumentV1 のスライスが格納される。fmt には各プレースホルダーに対応したフォーマット用の設定が入る(Option なのは何もフォーマット指定がない時の最適化のため)。

ArgumentV1 は次のように、対象となる変数への参照と出力用の関数で構成される。

src/libcore/fmt/mod.rs

pub struct ArgumentV1<'a> {
    value: &'a Void,
    formatter: fn(&Void, &mut Formatter) -> Result,
}

これらの構造体はそれぞれコンストラクタ Arguments::new_v1(), Arguments::new_v1::formatted(), ArgumentV1::new() を持っているが、unstable 扱いでありドキュメントからは隠蔽されている。そもそもこれらの値は format_args!() により生成されるため、通常はユーザ側で意識する必要はない。

実行時に Arguments の値を生成したい場合はフィーチャ fmt_internals を有効にする必要がある。

playground

#![feature(fmt_internals)]

use std::{fmt, io};

fn my_format_string(s: &String, f: &mut fmt::Formatter) -> fmt::Result {
    f.write_str(s.as_str())
}

fn main() {
    let name = String::from("Alice");
    let suffix = String::from("!\n");

    // args.len() <= pieces.len() <= args.len() + 1 が満たされている必要がある
    let pieces = &["Hello, ", suffix.as_str()];
    let args = &[fmt::ArgumentV1::new(&name, my_format_string)];
    let format_args = fmt::Arguments::new_v1(pieces, args);

    print(format_args);
}

fn print(args: fmt::Arguments) {
    let _ = io::Write::write_all(&mut io::stdout(), fmt::format(args).as_bytes());
}

core::fmt::write()

core::fmt::write(output, args) の中では大まかに次のような動作を行っている。

  • fmt::Formatter のインスタンスを生成する
  • pieces[i]args.arg[i].value を交互に出力する

おわりに

雑だが疲れたのでここまで。