マクロらしいけど、具体的にどうなっているのか気になったので追えるところまで追ってみた。
バージョンは1.21.0。
println!
定義はlibstd/macros.rsにある。
#[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!
#[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にある。
#[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にある。
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にある。
#[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
に指定されているもの。
thread_local! {
static LOCAL_STDOUT: RefCell<Option<Box<Write + Send>>> = {
RefCell::new(None)
}
}
スレッドローカルな値を指定しそうなマクロに引数として渡されている。
Noneを渡しているが、スレッドを使うときに別のものを指定するんだろうか?
スレッドとか並列処理は試したことがないので全然分からない。
追記
ユーザが使うのではなく、テストをするときなどに使われるらしい。
おわり
この辺りから型が混み合ってきたので切り上げる。