読者です 読者をやめる 読者になる 読者になる

Rustのstd::fmtモジュールの訳

Rustのstd::fmt(バージョン1.15.1)モジュールの訳です。

使い方

format!マクロはCのprintf/fprintf関数かPythonstr.format関数を使った経験がある人たちに馴染むようにしています。

format!拡張の例です:

format!("Hello");                 // => "Hello"
format!("Hello, {}!", "world");   // => "Hello, world!"
format!("The number is {}", 1);   // => "The number is 1"
format!("{:?}", (3, 4));          // => "(3, 4)"
format!("{value}", value=4);      // => "4"
format!("{} {}", 1, 2);           // => "1 2"
format!("{:04}", 42);             // => ゼロが前に付いた"0042"

これらから、第一引数が書式文字列であることが分かります。 コンパイラは第一引数が文字列リテラルであることを要求します。 つまり、(妥当性チェックを実行するために)変数を渡すことはできません。 そして、コンパイラは書式文字列の構文解析を行い、与えられた引数のリストをこの書式文字列へ渡すことが適切かどうかを確定させます。

位置引数

書式化引数それぞれに対応する値引数がどれなのかを指定することが可能で、省略した場合は"後続引数(the next argument)“であると見なします。 例えば、書式文字列{} {} {}は三つのパラメータを取り、そのパラメータと同じ順番で書式化されます。 書式文字列{2} {1} {0}は逆順で引数を書式化します。

この二種類の位置指定子を混ぜ始めたら、物事は少々扱いにくいものになります。 “後続引数"指定子は引数上のイテレータと考えることができます。 "後続引数"指定子が見つかる度に、イテレータが進みます。 その結果、このような動作になります:

format!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"

引数に対する内部イテレータは最初の{}が現れるまでは進まないので、第一引数を表示します。 それから、二番目の{}に到達すると同時に、イテレータは二番目の引数へ進みます。 基本的に、引数を明示的に指定するパラメータは、位置指定子における引数を指定しないパラメータに影響しません。

書式文字列は引数をすべて使っている必要があり、そうでなければ、コンパイル時エラーです。 引数を常に同じ型で参照しなければなりませんが、書式文字列の中で二回以上同じ引数を参照しても構いません。

名前付きパラメータ

Rust自体にPythonと同じような関数の名前付きパラメータは存在しませんが、format!マクロは名前付きパラメータを使用できる構文拡張です。 名前付きパラメータは引数リストの末尾に並べられ、このような構文を持ちます:

identifier '=' expression

例えば、次のformat!式はすべて名前付き引数を使っています:

format!("{argument}", argument = "test");   // => "test"
format!("{name} {}", 1, name = 2);          // => "2 1"
format!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"

名前を持つ引数の後に、位置引数(名前を持たない引数)を置くのは有効ではありません。 位置引数と同じく、書式文字列で使われない名前付きパラメータを与えることは有効ではありません。

引数の型

各引数の型は書式文字列により決定します。 すべての引数を常に一つの型でのみ参照する必要があります。 例えば、これは無効な書式文字列です:

{0:x} {0:o}

これは第一引数が八進数だけでなく十六進数としても参照されているため無効です。

しかし、特定の型を要求する様々なパラメータがあります。 例は(浮動小数点型における小数の桁数を設定する){:.*}構文です:

let formatted_number = format!("{:.*}", 2, 1.234567);

assert_eq!("1.23", formatted_number)

この構文を使う場合は、表示する文字数を書式化される実際のオブジェクトの前に置いて、さらに、文字数がusize型である必要があります。 usize{}を使って表示できますが、このように引数を参照することは無効です。 例えば、これもまた無効な書式文字列です:

{:.*} {0}

書式化トレイト

引数を特定の型で書式化することを要求すると、実際には引数が特定のトレイトに基づくことを要求することになります。 これにより、(isizeだけでなくi8もというように)複数の実際の型を{:x}を通して書式化できるようになります。 型からトレイトへの現在の対応関係です:

この意味は、fmt::Binaryトレイトを実装するどんな引数の型も{:b}で書式化することができるということです。 その上、標準ライブラリにより、多くのプリミティブ型に対するこれらのトレイトの実装が提供されています。 ({}または{:6}のように)書式が指定されない場合に、使用される書式トレイトはDisplayトレイトです。

自作の型に書式トレイトを実装するときは、このシグネチャのメソッドを実装する必要があります:

# #![allow(dead_code)]
# use std::fmt;
# struct Foo; // カスタムの型
# impl fmt::Display for Foo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
# write!(f, "testing, testing")
# } }

型は参照によるselfとして渡され、この関数がf.bufストリームへ出力を行います。 要求された書式化パラメータに適合させることは、各書式トレイト実装の義務です。 これらの書式化パラメータの値はFormatter構造体のフィールドに記録されます。 これを手助けするために、Formatter構造体はいくつかのヘルパーメソッドも提供しています。

加えて、関数の戻り値はResult<(), std::fmt::Error>の型エイリアスであるfmt::Resultです。 ただし、書式化実装は(例えば、write!を呼び出すときに)Formatterからエラーを確実に伝えるべきであり、決してエラーを偽って返してはいけません。 すなわち、渡されたFormatterがエラーを返すなら、書式化実装はエラーのみを返さなければならないし、かつ、エラーのみを返して差し支えありません。 なぜかというと、関数シグネチャが示唆することとは逆に、文字列書式化が失敗しない操作だからです。 元となるストリームへの書き込みが失敗するかもしれないため、この関数は結果を返すだけです。そして、発生したエラーがスタックを巻き戻す事実を伝える方法を提供しなければなりません。

書式化トレイトの実装の例はこのようなものです:

use std::fmt;

#[derive(Debug)]
struct Vector2D {
    x: isize,
    y: isize,
}

impl fmt::Display for Vector2D {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 値`f`は(write!マクロが期待する)`Write`トレイトを実装しています。
        // この書式化は書式文字列に与えられた様々なフラグを無視することに注意してください。
        write!(f, "({}, {})", self.x, self.y)
    }
}

// 様々なトレイトを実装すると、ある型を様々な形式で出力できるようになります。
// この書式はベクトルの大きさを表示するという意味です。
impl fmt::Binary for Vector2D {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let magnitude = (self.x * self.x + self.y * self.y) as f64;
        let magnitude = magnitude.sqrt();

        // Formatterオブジェクトのヘルパーメソッド`pad_integral`を使って、書式化フラグを無視しないようにしてください。
        // 詳細はこのメソッドのドキュメントを見てください。
        // また、関数`pad`は文字列をパッディングするために使用できます。
        let decimals = f.precision().unwrap_or(3);
        let string = format!("{:.*}", decimals, magnitude);
        f.pad_integral(true, "", &string)
    }
}

fn main() {
    let myvector = Vector2D { x: 3, y: 4 };

    println!("{}", myvector);       // => "(3, 4)"
    println!("{:?}", myvector);     // => "Vector2D {x: 3, y:4}"
    println!("{:10.3b}", myvector); // => "     5.000"
}

fmt::Displayfmt::Debug

これら二つの書式化トレイトには明確な目的があります:

  • fmt::Display実装は、その型を常にUTF-8文字列として正確に表現できるということを主張しています。 すべての型がDisplayトレイトを実装しているとは期待されていません

  • fmt::Debug実装はすべての公開された型(public types)に実装されるべきです。 通常、出力は内部状態をできるだけ忠実に表します。 Debugトレイトの目的はRustコードをデバッグし易くすることです。 ほとんどの場合は#[derive(Debug)]を使えば充分であり、また、使うことが推奨されています。

両方のトレイトからの出力のいくつかの例:

assert_eq!(format!("{} {:?}", 3, 4), "3 4");
assert_eq!(format!("{} {:?}", 'a', 'b'), "a 'b'");
assert_eq!(format!("{} {:?}", "foo\n", "bar\n"), "foo\n \"bar\\n\"");

関連マクロ

format!ファミリにはたくさんの関連マクロがあります。 現在、実装されているものです:

format!      // 上で説明しています
write!       // 第一引数は&mut io::Write(宛先)です
writeln!     // writeと同じですが、改行されます
print!       // 書式文字列は標準出力へ表示されます
println!     // printと同じですが、改行されます
format_args! // 下で説明しています。

write!

これとwritelnは書式文字列を特定のストリームへ出力する二つのマクロです。 これは書式文字列の中間割り当てを防いで、その代わりに直接出力を書き込むために使われます。 内部では、この関数はstd::io::Writeトレイトに定義されたwrite_fmt関数を実際には呼び出しています。 使い方の例です:

# #![allow(unused_must_use)]
use std::io::Write;
let mut w = Vec::new();
write!(&mut w, "Hello {}!", "world");

print!

これとprintlnは標準出力へ出力します。 write!マクロと同じく、これらのマクロの目的は出力を表示するときに中間割り当てを避けることです。 使い方の例です:

print!("Hello {}!", "world");
println!("I have a newline {}", "character at the end");

format_args!

これは書式文字列を記述する不透明オブジェクトを安全に渡すために使われる奇妙なマクロです。 このオブジェクトは作成にヒープ割り当てはまったく必要ありませんし、スタック上の情報を参照するだけです。 内部では、すべての関連マクロがこれに基づいて実装されています。 最初に、使い方の例です:

# #![allow(unused_must_use)]
use std::fmt;
use std::io::{self, Write};

let mut some_writer = io::stdout();
write!(&mut some_writer, "{}", format_args!("print with a {}", "macro"));

fn my_fmt_fn(args: fmt::Arguments) {
    write!(&mut io::stdout(), "{}", args);
}
my_fmt_fn(format_args!(", or a {} too", "function"));

format_args!マクロの結果はfmt::Arguments型の値です。 そして、書式文字列を処理するために、このモジュール内部にあるwriteformat関数へこの構造体を渡すことができます。 このマクロの目的は、書式文字列を扱うときに中間割り当てをさらに抑制することです。

例えば、ロギングライブラリは標準書式化構文を使いますが、出力先が決まるまで内部ではこの構造体を順に手渡していきます。

構文

使われている書式化言語の構文は他の言語から取られているので、見ず知らずではないでしょう。 引数はPythonに類似した構文(引数がCに似た%ではなく{}で囲まれるという意味)で書式化されます。 書式化構文の実際の文法です:

format_string := <text> [ format <text> ] *
format := '{' [ argument ] [ ':' format_spec ] '}'
argument := integer | identifier

format_spec := [[fill]align][sign]['#']['0'][width]['.' precision][type]
fill := character
align := '<' | '^' | '>'
sign := '+' | '-'
width := count
precision := count | '*'
type := identifier | ''
count := parameter | integer
parameter := argument '$'

書式化パラメータ

書式化される各引数は、(上の構文のformat_specに対応する)多くの書式化パラメータにより変形させられます。 これらのパラメータは書式化される対象の文字列表現に影響を与えます。 この構文はかなりの部分がPythonの構文から取られているので、多少は見たことがあるかもしれません。

fill/align

fill文字は通常widthパラメータと一緒に渡されます。 これは、書式化される値がwidthより小さい場合に、追加の文字がその周囲に表示されるということを表します。 追加の文字はfillにより指定され、そのアライメントは次のオプションの一つが指定できます:

  • < - 引数はwidth列の左揃えです
  • ^ - 引数はwidth列の中央揃えです
  • > - 引数はwidth列の右揃えです

型によってはアライメントを実装していない可能性があることに注意してください。 パディングが確実に適用されるようにする良い方法は、入力を書式化してできる文字列を出力に埋め込むように使うことです。

sign/#/0

これらはすべて特定の書式変換子(formatter)のためのフラグとして解釈されます。

  • + - これは数値型を対象としており、符号を常に表示するということを表します。 デフォルトでは、正の符号は決して表示されません。 また、デフォルトでは、負の符号はSignedトレイトに対してのみ表示されます。 このフラグを指定すると、正しい符号(+または-) が常に表示されます。
  • - - 現在、使われていません
  • # - このフラグは表示の"代替"形式が使われます。代替形式は:
    • #? - Debug書式化をプリティプリントする
    • #x - 引数の前に0xを置く
    • #X - 引数の前に0xを置く
    • #b - 引数の前に0bを置く
    • #o - 引数の前に0oを置く
  • 0 - これは整数書式に対して、符号により表記が変わるだけでなく文字0でパディングされることを示すために使われます。 {:08}のような書式は整数1に対して00000001を生成しますが、同じ書式が整数-1に対して-0000001を生成します。 負のバージョンは正のバージョンよりゼロが一つ少ないことに注目してください。

width

これは書式がとる"最小の幅"のパラメータです。 その値を表すの文字列がこの数の文字を満たさないならば、要求された幅を埋めるために指定されたfill/alignを使ってパディングを行います。

数値以外に対するデフォルトのfill/alignは空白一つと左揃えです。 数値書式変換子のためのデフォルトも空白一つですが、右揃えです。 0フラグが数値に対して指定された場合は、暗黙のfill文字は0です。

二番目の引数がwidthを指定するusizeであることを示すドル記号構文を使って、widthに対する値をパラメータのリストの中にusizeとして与えることができます:

// All of these print "Hello x    !"
println!("Hello {:5}!", "x");
println!("Hello {:1$}!", "x", 5);
println!("Hello {1:0$}!", 5, "x");
println!("Hello {:width$}!", "x", width = 5);

ドル記号構文で引数を参照しても"後続引数"カウンタには影響しないので、位置を使って引数を参照することは通常は良いアイディアです。 そうでない場合は、名前付き引数を使ってください。

precision

数値型以外に対しては、これは"最大の幅"と考えることができます。 生成される文字列がこの幅より大きい場合は、この数の文字まで切り捨てます。そして、以下のパラメータが設定されているなら、切り捨てられた値は適切なfillalignおよびwidthにしたがって出力します。

整数型に対しては、これを無視します。

浮動小数点型に対しては、これは小数点の後ろに何文字表示されるかを表します。

所望のprecisionを指定するための三種類の方法があります:

  1. 整数 .N:

    整数N自体が精度です。

  2. 後ろにドル記号が続く整数または名前 .N$:

    precisionとして、(usizeでなければならない)書式引数Nを使ってください。

  3. アスタリスク .*:

    .*が意味するのは、この{...}は一つではなく二つの書式入力と関係付けられているということです: 最初の入力がusizeのprecisionを、二番目が表示する値を保持します。 この場合に、書式文字列{<arg>:<spec>.*}を使うなら、<arg>部分は表示する値を参照し、precisionは<arg>の前の入力に与えられなければならないことに注意してください。

例えば、次の呼び出しはすべて同じものHello x is 0.01000を表示します:

// Hello {引数0 ("x")} is {インラインで指定された精度(5)を持つ引数1 (0.01)}
println!("Hello {0} is {1:.5}", "x", 0.01);

// Hello {引数1 ("x")} is {引数0に指定された精度(5)を持つ引数2 (0.01)}
println!("Hello {1} is {2:.0$}", 5, "x", 0.01);

// Hello {引数0 ("x")} is {引数1に指定された精度(5)を持つ引数2 (0.01)}
println!("Hello {0} is {2:.1$}", "x", 5, 0.01);

// Hello {後続引数 ("x")} is {続く二つの引数の一番目に指定された精度(5)を持つ続く二つの引数の二番目 (0.01)}
println!("Hello {} is {:.*}",    "x", 5, 0.01);

// Hello {後続引数 ("x")} is {引数2の前に指定された精度(5)を持つ引数2 (0.01)}
println!("Hello {} is {2:.*}",   "x", 5, 0.01);

// Hello {後続引数 ("x")} is {引数"prec"に指定された精度(5)を持つ引数"number" (0.01)}
println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);

それに対して、これらは:

println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");

二つの著しく異なるものを表示します:

Hello, `1234.560` has 3 fractional digits
Hello, `123` has 3 characters
Hello, `     123` has 3 right-aligned characters

エスケープ

文字{}そのものは同じ文字を前に置くことで文字列に含められます。 例えば、{文字は{{でエスケープされ、}文字は}}でエスケープされます。

構造体

Arguments : この構造体は、書式文字列とその引数の安全に事前コンパイルされたバージョンを表します。これを実行時に生成することはできません。なぜなら、安全にそうすることができないからです。したがって、コンストラクタはありません。また、フィールドは変更を防ぐためにプライベートです。

DebugList : fmt::Debug実装を手助けするための構造体。

DebugMap : fmt::Debug実装を手助けするための構造体。

DebugSet : fmt::Debug実装を手助けするための構造体。

DebugStruct : fmt::Debug実装を手助けするための構造体。

DebugTuple : fmt::Debug実装を手助けするための構造体。

Error : ストリームへメッセージを初期化する処理から返されるエラー型。

Formatter : 文字列を出力する場所と文字列が書式化される方法の両方を表す構造体。これの変更可能なバージョンがすべての書式化トレイトへ渡されます。

トレイト

Binary : 文字bの書式トレイト。

Debug : 文字?の書式トレイト。

Display : 空書式({})の書式トレイト。

LowerExp : 文字eの書式トレイト。

LowerHex : 文字xの書式トレイト。

Octal : 文字oの書式トレイト。

Pointer : 文字pの書式トレイト。

UpperExp : 文字Eの書式トレイト。

UpperHex : 文字Xの書式トレイト。

Write : ストリームへメッセージを書式化して出力するために必要なメソッドを集めたもの。

関数

format : format関数は事前コンパイルされた書式文字列と引数のリストを取り、結果の書式化された文字列を返します。

write : write関数は出力ストリーム、事前コンパイルされた書式文字列、および、引数のリストを取ります。引数は指定された書式文字列にしたがって書式化され、与えられた出力ストリームへ出力されます。

型定義

Result : formatterメソッドが返す型。