Open7

[rust] format, print

shiratorishiratori

$の挙動

fn main() {
    println!("Hello {}!", "Rust");
    println!("Hello {:5}!", "Rust");
    println!("Hello {:1$}!", "Rust", 1);
    println!("Hello {:1$}!", "Rust", 5);
    println!("Hello {1:0$}!", 5, "Rust");
    println!("Hello {:width$}!", "Rust", width = 5);
    let width = 5;
    println!("Hello {:width$}!", "Rust");
}

実行結果

Hello Rust!
Hello Rust !
Hello Rust!
Hello Rust !
Hello Rust !
Hello Rust !
Hello Rust !
shiratorishiratori
メソッド 説明
format! フォーマットされたテキストを文字列(String)型に書き込みます。
print! format! と同様ですが、コンソール (io::stdout) にそのテキストを出力します。
println! print! と同じですが改行が付け加えられます。
eprint! format! と同様ですが、標準エラー出力 (io::stderr) にそのテキストを出力します。
eprintln! eprint!と同じですが改行が付け加えられます。
shiratorishiratori

Named parameters

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"
let argument = 2 + 2;
format!("{argument}");   // => "4"

fn make_string(a: u32, b: &str) -> String {
    format!("{b} {a}")
}
make_string(927, "label"); // => "label 927"
shiratorishiratori

Formatting Parameters

Width

//これらはすべて「Hellox!」と出力します
println!("Hello {:5}!", "x");
println!("Hello {:1$}!", "x", 5);
println!("Hello {1:0$}!", 5, "x");
println!("Hello {:width$}!", "x", width = 5);
let width = 5;
println!("Hello {:width$}!", "x");

Fill/Alignment

println!("Hello {:<5}!", "x");
    println!("Hello {:-<5}!", "x");
    println!("Hello {:^5}!", "x");
    println!("Hello {:>5}!", "x");

実行結果

Hello x    !
Hello x----!
Hello   x  !
Hello     x!

Sign/#/0

println!("Hello {:+}!", 5);
println!("{:#x}!", 27);
println!("Hello {:05}!", -5);
println!("Hello {:+}!", 5);
println!("{:#010x}!", 27);

実行結果

Hello +5!
0x1b!
Hello -0005!
Hello +5!
0x0000001b!

これらはすべてフォーマッタの動作を変更するフラグです。

  • + これは数値タイプを対象としており、記号を常に印刷する必要があることを示します。正の符号はデフォルトで印刷されることはなく、負の符号は符号付きの値に対してのみデフォルトで印刷されます。このフラグは、正しい符号( + または - )を常に出力する必要があることを示します。
  • - 現在使用されていません
  • # このフラグは、「代替」形式の印刷を使用する必要があることを示します。別の形式は次のとおりです。
    • #? -pretty- Debug フォーマットを印刷します(改行とインデントを追加します)
    • #x 引数の前に 0x を付ける
    • #X 引数の前に 0x を付ける
    • #b 引数の前に 0b を付ける
    • #o 引数の前に 0o を付ける
  • 0 これは、整数形式の場合、 width へのパディングは 0 文字で行う必要があり、符号を認識する必要があることを示すために使用されます。 {:08} のような形式では、整数 1 に対して 00000001 が生成されますが、同じ形式では、整数 -1 に対して -0000001 が生成されます。負のバージョンのゼロは正のバージョンより1つ少ないことに注意してください。パディングゼロは常に記号(存在する場合)の後、数字の前に配置されることに注意してください。 # フラグと一緒に使用すると、同様のルールが適用されます。接頭辞の後、数字の前にパディングゼロが挿入されます。プレフィックスは全幅に含まれます。

Precision

非数値タイプの場合、これは「最大幅」と見なすことができます。結果の文字列がこの幅よりも長い場合、この数の文字に切り捨てられ、これらのパラメータが設定されている場合、その切り捨てられた値は適切な fill 、 alignment 、および width

積分型の場合、これは無視されます。

浮動小数点型の場合、これは小数点以下の桁数を表示することを示します。

必要な precision を指定するには、次の3つの方法があります。

整数 .N :

整数 N 自体が精度です。

整数または名前とそれに続くドル記号 .N$ :

精度としてフォーマット引数 N ( usize である必要があります)を使用します。

アスタリスク .* :

.* は、この {...} が1 つではなく2 つの形式入力に関連付けられていることを意味します。

{:<spec>.} の形式のフォーマット文字列が使用されている場合、最初の入力は使用 usize を保持し、2 番目の入力は出力する値を保持します。
{<arg>:<spec>.
} の形式のフォーマット文字列が使用されている場合、 < precision <arg> 部分は出力する値を参照し、省略された位置パラメーター ( {} {<arg>:} の代わりに{} )。 precision {} {<arg>:}
たとえば、次の呼び出しはすべて同じものを出力します Hello x is 0.01000 :

// Hello {arg 0 ("x")} is {arg 1 (0.01) with precision specified inline (5)}
println!("Hello {0} is {1:.5}", "x", 0.01);

// Hello {arg 1 ("x")} is {arg 2 (0.01) with precision specified in arg 0 (5)}
println!("Hello {1} is {2:.0$}", 5, "x", 0.01);

// Hello {arg 0 ("x")} is {arg 2 (0.01) with precision specified in arg 1 (5)}
println!("Hello {0} is {2:.1$}", "x", 5, 0.01);

// Hello {next arg -> arg 0 ("x")} は {次の 2 つの引数の 2 番目 -> arg 2 (0.01) の精度
// 次の 2 つの引数の最初に指定 -> 引数 1 (5)}
println!("Hello {} is {:.*}",    "x", 5, 0.01);

// Hello {arg 1 ("x")} is {arg 2 (0.01) with precision
// 次の引数で指定 -> 引数 0 (5)}
println!("Hello {1} is {2:.*}",  5, "x", 0.01);

// Hello {next arg -> arg 0 ("x")} は {arg 2 (0.01) with precision
// 次の引数で指定 -> 引数 1 (5)}
println!("Hello {} is {2:.*}",   "x", 5, 0.01);

// Hello {next arg -> arg 0 ("x")} is {arg "number" (0.01) with precision specified
//                          in arg "prec" (5)}
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
shiratorishiratori

Formatting traits

引数を特定のタイプでフォーマットするように要求する場合、実際には、引数が特定の特性に起因することを要求しています。これにより、複数の実際の型を {:x} を介してフォーマットできます( i8 や isize など)。タイプとトレイトの現在のマッピングは次のとおりです。

  • 何もない⇒ Display
  • ? ⇒ Debug
  • x? ⇒小文字の16進整数で Debug
  • X? ⇒大文字の16進整数で Debug
  • o ⇒8進 Octal
  • x ⇒LowerHe _ LowerHex
  • X ⇒UpperHex _ UpperHex
  • p ⇒ Pointer
  • b ⇒ Binary
  • e ⇒LowrExp _ LowerExp
  • E ⇒Upperxp _ UpperExp

これは、 fmt::Binary トレイトを実装する任意のタイプの引数を {:b} でフォーマットできることを意味します。標準ライブラリによって、いくつかのプリミティブ型のこれらの特性の実装も提供されています。フォーマットが指定されていない場合( {} または {:6} など)、使用されるフォーマット特性は Display 特性です。

自身の型にフォーマットの形質を実装する場合は、署名のメソッドを実装する必要があります。

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

あなたの型は self fmt::Write を実装する Formatter f に出力する必要があります。要求されたフォーマット パラメータに正しく準拠するかどうかは、各フォーマット トレイトの実装次第です。 Formatter 構造体のメソッドを使用してアクセスできます。これを支援するために、 Formatter 構造体はいくつかのヘルパー メソッドも提供します。 fmt::Write Formatter Formatter

さらに、この関数の戻り値は fmt::Result であり、これは Result<(), std::fmt::Error> 型エイリアスです。フォーマットの実装では、 Formatter からのエラーを確実に伝播する必要があります(たとえば、 write! を呼び出す場合)。ただし、誤ってエラーを返すことはありません。つまり、フォーマットの実装は、渡された 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!(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!("{myvector:10.3b}"); // => "     5.000"
}

fmt::Display と fmt::Debug
この2つの書式設定の特徴は、それぞれ異なる目的を持っています。

fmt::Display 実装は、型が常にUTF-8文字列として忠実に表現できることを表明します。すべてのタイプが Display トレイトを実装するとは限りません。
fmt::Debug 実装は、すべてのパブリックタイプに対して実装する必要があります。出力は通常、内部状態をできるだけ忠実に表します。 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\"");