Rustのprintln!の中身

マクロらしいけど、具体的にどうなっているのか気になったので追えるところまで追ってみた。
バージョンは1.21.0。

# println!
定義はlibstd/macros.rsにある。

```rust:println!
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
macro_rules! println {
    () => (print!("\n"));
    ($fmt:expr) => (print!(concat!($fmt, "\n")));
    ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}
```

`macro_export`と`stable`の2つのアトリビュートがある。
前者はマクロをエクスポートするためのもので、後者はコンパイル時に使用する機能の制限だろうか？
動作にはあまり関係なさそうなのであまり掘り下げずに中身を見る。

`println!`は`print!`のラッパーで、改行を付加するようになっているらしい。
上から、引数なし、引数がただの文字列リテラル、引数にフォーマット文字列ありのパターンになっている。
ソースコードのコメントには、改行はどのプラットフォームでもLine Feed(U+000A,\\n)になるとある。
データの書き出しには`format!`を使え、エラーとプログレスには`eprintln!`を代わりに使え、とも書いてあった。
標準出力への書き出しが失敗するとパニックを起こす。

# print!
```rust:print!
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable]
macro_rules! print {
    ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*)));
}
```

`print!`内では、引数を`format_args!`で処理したあと、`create::io::_print`に渡している。
`allow_internal_unstable`はマクロの拡張の中でunstableなことをするときの警告を抑制するためのアトリビュートらしい。
~~おそらく出力に失敗したときのパニックのこととかだと思う。~~
APIが不安定な機能を使うためのものだそうです。

`format_args!`はコンパイラ組み込みの機能らしく、ダウンロードしたソースには何も実装されていなかった。
他のフォーマット系のマクロもここを通るようになっている。

この辺からマクロじゃなく関数になるのでracerのリファレンス機能が使える、万歳。

# `crate::io::_print`
定義はlibstd/io/stdio.rsにある。

```rust:_print
#[unstable(feature = "print_internals",
           reason = "implementation detail which may disappear or be replaced at any time",
           issue = "0")]
#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
    print_to(args, &LOCAL_STDOUT, stdout, "stdout");
}
```

最初の`unstable`アトリビュートはドキュメントとunstableの指定のためだと思われる。
`doc`は'///'のコメントと同じらしい。むしろ///がシンタックスシュガーなんだろうか?
`fmt::Arguments`は`format_args!`の返り値の型である。
次は`print_to`である。

# `print_to`
stdio.rsにある。

```rust:print_to
fn print_to<T>(args: fmt::Arguments,
               local_s: &'static LocalKey<RefCell<Option<Box<Write+Send>>>>,
               global_s: fn() -> T,
               label: &str) where T: Write {
    let result = match local_s.state() {
        LocalKeyState::Uninitialized |
        LocalKeyState::Destroyed => global_s().write_fmt(args),
        LocalKeyState::Valid => {
            local_s.with(|s| {
                if let Ok(mut borrowed) = s.try_borrow_mut() {
                    if let Some(w) = borrowed.as_mut() {
                        return w.write_fmt(args);
                    }
                }
                global_s().write_fmt(args)
            })
        }
    };
    if let Err(e) = result {
        panic!("failed printing to {}: {}", label, e);
    }
}
```

`args`は表示する文字列、可能なら`local_s`に、不可能だったら`global_s`に表示する。
`label`はエラーメッセージ用の識別子。
`_print`ではlocal_sは`&LOCA_STDOUT`、`global_s`は`stdout`、`label`は`"stdout"`となっている。

`local_s`が初期化されていないか、`Destroyed`になっているなら`global_s`に出力する。
`Valid`なら`local_s`に出力するらしい、クロージャを渡し、その中で出力している。
その後でさらに`global_s()`に対して書き込みをしている。

`Err`が返されたらパニックを起こしている。

# stdout
global_sに指定されているstdoutは標準出力を返すような関数になっているようだ。
stdio.rsにある。

```rust:stdout
#[stable(feature = "rust1", since = "1.0.0")]
pub fn stdout() -> Stdout {
    static INSTANCE: Lazy<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>>
        = Lazy::new(stdout_init);
    return Stdout {
        inner: INSTANCE.get().expect("cannot access stdout during shutdown"),
    };

    fn stdout_init() -> Arc<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>> {
        let stdout = match stdout_raw() {
            Ok(stdout) => Maybe::Real(stdout),
            _ => Maybe::Fake,
        };
        Arc::new(ReentrantMutex::new(RefCell::new(LineWriter::new(stdout))))
    }
}
```

おそらく、内部で定義している`stdout_init`を遅延評価しそうな`Lazy`の中に入れて返しているのだろうか?
`stdout_init`では`stdout_raw`からstdoutを取得するような感じらしい。
`Stdout`では`lock()`を通してロックを確保してから出力やフラッシュなどの処理を実行しているらしい。
`lock()`の返り値へのラッパー関数となっているらしかった。
ソース見てたら`LineWriter`か`BufWriter`かどうかについてFIXMEが書いてあった。このソースだと`LineWriter`になっている。

ロックを取ってから出力するような例がコメントにあった。
その場合、`local_s`に指定された`stdout`を使い、`lock`を取得するらしい。

# LOCAL_STDOUT
`local_s`に指定されているもの。

```rust:LOCAL_STDOUT
thread_local! {
    static LOCAL_STDOUT: RefCell<Option<Box<Write + Send>>> = {
        RefCell::new(None)
    }
}
```

スレッドローカルな値を指定しそうなマクロに引数として渡されている。
Noneを渡しているが、スレッドを使うときに別のものを指定するんだろうか?
スレッドとか並列処理は試したことがないので全然分からない。
追記
ユーザが使うのではなく、テストをするときなどに使われるらしい。

# おわり
この辺りから型が混み合ってきたので切り上げる。