当我们用cargo new XXX创建一个rust项目时,默认都会生成main.rs文件,里面的代码就是println!("Hello, world!");用于向控制台打印Hello world。因此,我打算通过标准库源码对println宏的实现机制进行深入剖析。
首先,看一下声明宏println!的实现源码:
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable(print_internals, format_args_nl)]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ({
$crate::io::_print($crate::format_args_nl!($($arg)*));
})
}
可以看到,println宏间接调用了format_args_nl宏,先对打印的内容进行格式化,然后通过标准库io模块的_print方法进行打印。接下来,我们看一下format_args_nl宏的实现源码:
/// Same as `format_args`, but adds a newline in the end.
#[unstable(
feature = "format_args_nl",
issue = "none",
reason = "`format_args_nl` is only for internal \
language use and is subject to change"
)]
#[allow_internal_unstable(fmt_internals)]
#[doc(hidden)]
#[rustc_builtin_macro]
#[macro_export]
macro_rules! format_args_nl {
($fmt:expr) => {{ /* compiler built-in */ }};
($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
}
代码中可以看到,format_args_nl宏都是由编译器内在实现的,不是由标准库实现,对于编译器的实现我也不熟悉,就不深入讨论了。
回过头看,看一下具体的标准库io模块的_print方法实现源码:
#[unstable(
feature = "print_internals",
reason = "implementation detail which may disappear or be replaced at any time",
issue = "none"
)]
#[doc(hidden)]
#[cfg(not(test))]
pub fn _print(args: fmt::Arguments<'_>) {
print_to(args, stdout, "stdout");
}
里面调用print_to方法,具体看代码:
/// Write `args` to the capture buffer if enabled and possible, or `global_s`
/// otherwise. `label` identifies the stream in a panic message.
///
/// This function is used to print error messages, so it takes extra
/// care to avoid causing a panic when `local_s` is unusable.
/// For instance, if the TLS key for the local stream is
/// already destroyed, or if the local stream is locked by another
/// thread, it will just fall back to the global stream.
///
/// However, if the actual I/O causes an error, this function does panic.
fn print_to(args: fmt::Arguments<'_>, global_s: fn() -> T, label: &str)
where
T: Write,
{
if OUTPUT_CAPTURE_USED.load(Ordering::Relaxed)
&& OUTPUT_CAPTURE.try_with(|s| {
// Note that we completely remove a local sink to write to in case
// our printing recursively panics/prints, so the recursive
// panic/print goes to the global sink instead of our local sink.
s.take().map(|w| {
let _ = w.lock().unwrap_or_else(|e| e.into_inner()).write_fmt(args);
s.set(Some(w));
})
}) == Ok(Some(()))
{
// Succesfully wrote to capture buffer.
return;
}
if let Err(e) = global_s().write_fmt(args) {
panic!("failed printing to {}: {}", label, e);
}
}
这个方法的操作是:如果可能的话,把' args '写入到捕获缓冲区,否则写入到标准输出。