Print Macros
Three macros for easy printing, mirroring the standard library API:
print!(...)— Print without newlineprintln!(...)— Print with newlineprintk!(...)— Kernel print (alias forprint!, named after the Linux kernel function)
How They Work
The Print Function
#![allow(unused)]
fn main() {
pub fn _print(args: fmt::Arguments) {
use core::fmt::Write;
unsafe {
let writer = &mut *core::ptr::addr_of_mut!(WRITER);
writer.write_fmt(args).unwrap();
}
}
}
fmt::Arguments is the type produced by format_args!(). It’s a lazily-evaluated format string — nothing is formatted until write_fmt is called. This keeps the hot path efficient.
impl fmt::Write for Writer is what makes this work — it requires a single method:
#![allow(unused)]
fn main() {
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_string(s);
Ok(())
}
}
}
The Macros
#![allow(unused)]
fn main() {
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::vga::_print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
#[macro_export]
macro_rules! printk {
($($arg:tt)*) => ($crate::print!($($arg)*));
}
}
#[macro_export] makes the macros available at the crate root, so any module can use them without importing.
The $($arg:tt)* pattern captures any token tree — it’s the standard Rust pattern for variadic macro arguments that get passed to format_args!.
Usage
#![allow(unused)]
fn main() {
println!("KFS_1 - Kernel From Scratch");
println!("Stack pointer: 0x{:08x}", esp);
print!("Loading");
printk!("."); // Kernel-style progress dots
}
The #[macro_use] Pattern
In lib.rs, the VGA module is declared with #[macro_use]:
#![allow(unused)]
fn main() {
#[macro_use]
mod vga;
}
This makes all #[macro_export] macros from vga available throughout the crate without needing use statements. Since print! and println! are used everywhere, this saves a lot of boilerplate.