LoginSignup
30
12

More than 5 years have passed since last update.

Rustのprintln!の中身

Last updated at Posted at 2017-11-05

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

println!

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

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

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

print!

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にある。

_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::Argumentsformat_args!の返り値の型である。
次はprint_toである。

print_to

stdio.rsにある。

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_STDOUTglobal_sstdoutlabel"stdout"となっている。

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

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

stdout

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

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()の返り値へのラッパー関数となっているらしかった。
ソース見てたらLineWriterBufWriterかどうかについてFIXMEが書いてあった。このソースだとLineWriterになっている。

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

LOCAL_STDOUT

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

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

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

おわり

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

30
12
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
12