格式化输出
本章代码对应分支:fmt-print
概要
通过上一章,我们已经可以在屏幕上打印简单的字符串了,但是我们不应该满足于此。本章我们将实现 rust 中最经典的宏: println! ,用于后续的调试和输出。这需要我们对 rust 的一些特性有一定的了解:
- 宏的使用。
- trait 的特性。
简单打印字符和字符串
在一个文件内实现过多的功能会使得文件过于冗长,不易阅读与维护,所以我们(在 main.rs 的同级目录下)创建一个新的文件用于管理 io 。现在我们来为 io 实现两个最简单的函数:
io.rs
use crate::sbi;
pub fn putchar(ch: char) {
sbi::console_putchar(ch as u8 as usize);
}
pub fn puts(s: &str) {
for ch in s.chars() {
putchar(ch);
}
}
lib.rs
pub mod io;
修改 rust_main 为:
#[no_mangle]
pub fn rust_main() -> ! {
crate::io::puts("666666");
loop {}
}
编译运行,屏幕成功输出了 666666 !
实现 println!
io.rs
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ({
$crate::io::_print(format_args!($($arg)*));
});
}
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
#[macro_export]
宏使得外部的库也可以使用这个宏。 format_args!
宏可以将 print(...)
内的部分转换为 fmt::Arguments
类型,用以后续打印。这里我们用到了一个还未实现的函数: _print
。他的实现方法十分神奇,借助了 rust 奇妙的语法。
rust trait 有点类似 C++ 的抽象类,实现 trait 有点类似继承抽象类。这里我们创建了一个空的结构体,但是,由于它实现了 fmt::Write::write_str
,trait 会帮我们自动实现 fmt::Write::write_fmt
。
如果你想进一步了解这部分内容,可以参考 rust 官方文档 - core::fmt::Write 和 rust 官方教程 - Traits 部分
io.rs
use core::fmt::{self, Write};
struct StdOut;
impl fmt::Write for StdOut {
fn write_str(&mut self, s: &str) -> fmt::Result {
puts(s);
Ok(())
}
}
pub fn _print(args: fmt::Arguments) {
StdOut.write_fmt(args).unwrap();
}
为了在外部中使用 io.rs
中的宏,我们需要在 pub mod io
的上方添加属性:
#[macro_use]
mod io;
现在我们可以让 println!
进行一些“高难度”的工作,比如打印 panic 信息:
lang_items.rs
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
println!("{}", info);
loop {}
}
init.rs
#[no_mangle]
pub fn rust_main() -> ! {
let a = "Hello";
let b = "World";
println!("{}, {}!", a, b);
panic!("End of rust_main");
}
再次编译运行,程序输出:
Hello, World!
panicked at 'End of rust_main', src/main.rs:25:5
预告
当 CPU 访问无效的寄存器地址,或进行除零操作,或者进行 系统调用 时,会产生中断。下一章,我们将实现一个简单的中断机制对这些情况进行处理。