# 3. 格式化输出

> 本章代码对应 commit ：329c65f867880986732987614d1135a6982cff84

## 概要

通过上一章，我们已经可以在屏幕上打印简单的字符串了。但是这并不足够，本章我们将实现 rust 中最经典的宏： **println!** ，以便于后续的调试输出。这需要我们对 rust 的一些特性有一定的了解：

1. 宏的使用。
2. trait 的特性。

## 简单打印字符和字符串

在一个文件内实现过多的功能会使得文件过于冗长，不易阅读与维护，所以我们（在 main.rs 的同级目录下）创建一个新的文件用于管理 **io** 。现在我们来为 **io** 实现两个最简单的函数：

```rust
// in io.rs

use bbl::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);
    }
}
```

从函数名可以看出，这两个函数的功能分别是 **打印一个字符** 和 **打印 str** 。

在 **main.rs** 中引入 **io** 库：

```rust
pub mod io;
```

修改 **rust\_main** 为：

```rust
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
    io::puts("666666");
    loop {}
}
```

编译运行，屏幕成功输出了 **666666** ！

## 支持println!宏执行

很显然，要完成 **println!** ， **print!** 是必不可少的。那我们就先来实现 **print!** ：

```rust
// in io.rs

#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ({
        $crate::io::_print(format_args!($($arg)*));
    });
}
```

`#[macro_export]` 宏使得外部的库也可以使用这个宏。 **format\_args!** 宏可以将 print(...) 内的部分转换为 **fmt::Arguments** 类型，用以后续打印。这里我们用到了一个还未实现的函数： **\_print** 。他的实现方法十分神奇，现在让我们先来做一些准备工作：

```rust
// in 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(())
    }
}
```

我们引入了 **fmt::Write 特征（trait）** ，创建了一个新的类： **StdOut** 。这里我们为 **StdOut** 实现了他的 **trait** 。接下来，就让我们来实现 **\_print** 吧：

```rust
// in io.rs

pub fn _print(args: fmt::Arguments) {
    StdOut.write_fmt(args).unwrap();
}
```

细心的你可能已经发现， **write\_fmt** 和我们上一步实现的函数并不一样。这不是笔误，反而是前面所提到的 **神奇之处** 。由于我们实现了 **write\_str** ，核心库会帮我们自动实现 **write\_fmt** 。如果你想进一步了解这部分内容，可以阅读 [rust 官方文档中 core::fmt::Write 部分](https://doc.rust-lang.org/core/fmt/trait.Write.html) 和 [rust 官方教程中 Traits 部分](https://doc.rust-lang.org/book/ch10-02-traits.html) 。

完成了上述所有步骤后，我们的 io.rs 应该是这个样子的：

```rust
use bbl::sbi;
use core::fmt::{self, Write};

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);
    }
}

#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ({
        $crate::io::_print(format_args!($($arg)*));
    });
}

pub fn _print(args: fmt::Arguments) {
    StdOut.write_fmt(args).unwrap();
}

struct StdOut;

impl fmt::Write for StdOut {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        puts(s);
        Ok(())
    }
}
```

为了在 **main.rs** 中使用 **io.rs** 中的宏，我们需要在 `pub mod io` 的上方添加属性：

```rust
#[macro_use]
pub mod io;
```

然后修改 **rust\_main** ：

```rust
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
    let a = "Hello";
    let b = "World";
    print!("{}, {}!", a, b);
    loop {}
}
```

编译运行！可以看到，我们的 os 如预期一样，输出了 **Hello World!** 。在高兴之前，先让我们完成最后一步，编写 **println!** ：

```rust
#[macro_export]
macro_rules! println {
    () => ($crate::print!("\n"));
    ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
```

现在我们可以让 **println!** 进行一些更高难度的工作，打印 **panic** 信息。首先，修改 **panic** 函数为：

```rust
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    println!("{}", info);
    loop {}
}
```

然后将 **rust\_main** 中的无限循环替换为：

```rust
panic!("End of rust_main");
```

完成这些后，我们的 **main.rs** 应该长这样：

```rust
#![no_std] // don't link the Rust standard library
#![no_main] // disable all Rust-level entry points
#![feature(global_asm)]

#[macro_use]
pub mod io;

use core::panic::PanicInfo;

global_asm!(include_str!("boot/entry.asm"));

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    println!("{}", _info);
    loop {}
}

#[no_mangle]
pub extern "C" fn rust_main() -> ! {
    let a = "Hello";
    let b = "World";
    println!("{}, {}!", a, b);
    panic!("End of rust_main");
}

#[no_mangle]
pub extern fn abort() {
    panic!("abort!");
}
```

再次编译运行，程序输出：

```
Hello, World!
panicked at 'End of rust_main', src/main.rs:25:5
```

## 预告

当 CPU 访问无效的寄存器地址，或进行除零操作，或者进行 **系统调用** 时，会产生中断。下一章，我们将实现一个简单的中断机制对这些情况进行处理。
