tree: ff2735c982273faf580811fa5cbe1600aca59f9d
  1. macro/
  2. src/
  3. BUILD.gn
  4. OWNERS
  5. README.md
src/lib/kprint/README.md

kprint

kprint is a zero-overhead, stack-efficient logging and formatting crate designed for systems and embedded Rust code in the Zircon kernel environment.

It allows developers to write ergonomic Rust format strings ({}) that produce the same output as standard Rust formatting (core::fmt) while compiling invocations directly down to C printf / snprintf calls without intermediate stack buffers.

Overview & Architecture

Standard Rust formatting (core::fmt::write!, format_args!, and println!) relies heavily on core::fmt::Formatter, trait objects, dynamic dispatch tables, and intermediate stack formatting buffers. In resource-constrained environments or bare-metal kernels, this introduces unwanted binary bloat and stack space overhead.

kprint solves this by translating Rust format strings and arguments at compile time into C printf / snprintf invocations that preserve Rust's formatting output semantics:

  1. Rust Output Fidelity: Accuracy is defined by matching the output that standard Rust formatting (core::fmt) would produce, rather than performing a naive 1-to-1 syntax mapping to C printf specifiers. Where C printf differs from Rust (for example, C‘s %#x omits the 0x prefix when formatting 0 and %#X emits uppercase 0X, whereas Rust’s {:#x} and {:#X} always emit lowercase 0x (0x0, 0x2A), and strings default to left-alignment), kprint synthesizes the appropriate C format string and arguments (such as "0x%llx", "0x%06llX", "%*s%llx" with a computed prefix width, or "%-8.*s") so the resulting output matches Rust without allocating stack buffers.
  2. Compile-Time Format Translation: A procedural macro parses Rust format strings (e.g., "{:04x}" or "{:10s}") and converts them into static, null-terminated C format string literals (b"%04llx\0" or b"%-10.*s\0").
  3. Trait-Based Argument Marshaling: Formatting arguments are bound once to local variables and converted via lightweight backend traits and helpers (AsKPrintStr, AsKPrintCString, AsKPrintSignedInt, AsKPrintUnsignedInt, AsKPrintPointer, AsKPrintChar, hex_alt_prefix_width).
  4. Minimal Stack & Zero Heap Allocation: When formatting slices (&str, &[u8], &CStr), kprint passes (slice.len(), slice.as_ptr()) to C %.*s specifiers, avoiding heap allocations or temporary string-copying buffers.
  5. Single Evaluation Semantics: Every format expression is evaluated strictly once, guaranteeing safe usage with side-effecting expressions.
  6. Direct C Backend: Invocations compile directly into printf calls (or snprintf for kformat!), avoiding core::fmt dispatch tables.

Usage

Basic Printing

use kprint::{kprint, kprintln};

fn example() {
    let score = 42;
    let user = "alice";

    // Standard output without newline using captured variables
    kprint!("Connecting user {user:s} with ID {score:#06x}...");

    // Standard output with newline
    kprintln!("Connection established.");
}

In-Memory Formatting (kformat!)

When you need to format into a stack buffer without allocating:

use kprint::kformat;

fn format_example() {
    let mut buffer = [0u8; 128];
    let result: &[u8] = kformat!(&mut buffer, "Status: {:#08X} ({})", 0x1A, true);
    assert_eq!(result, b"Status: 0x00001A (true)");
}

Advanced Formatting Features

// Compile-time string concatenation
kprintln!(concat!("prefix_", "status: {}"), 42);

// Named and positional arguments
kprintln!("{0} + {1} = {0}", "a", "b");
kprintln!("{greeting:s}, {name:s}!", greeting = "hello", name = "world");

// Null-terminated C string pointers (*const c_char)
let c_ptr = c"kernel".as_ptr();
kprintln!("booting {:cs}", c_ptr);

Supported Format Variations

The procedural macro infers C format families from literal types, cast expressions, or explicit format specifiers, translating them into C printf specifiers and arguments that match Rust's formatting behavior:

Rust Specifier / TypeGenerated C SpecifierC ABI Argument Cast / TraitExample Output
{}, {:d}, i32, isize%lldAsKPrintSignedInt (c_longlong)-12345
{:u}, u32, usize%lluAsKPrintUnsignedInt (c_ulonglong)12345
{:x}, {:X}%llx, %llXAsKPrintUnsignedInt (c_ulonglong)abcd, ABCD
{:#x}, {:#X}0x%llx, 0x%llXAsKPrintUnsignedInt (c_ulonglong)0x0, 0x2A
{:#08X}0x%06llXAsKPrintUnsignedInt (c_ulonglong)0x00001A
{:<#10x}0x%-8llxAsKPrintUnsignedInt (c_ulonglong)0x2a
{:#8x}, {:>#8x}%*s%llxhex_alt_prefix_width, "0x", AsKPrintUnsignedInt 0x1a, 0x0
{:p}, {:#p}0x%llx, 0x%016llxAsKPrintPointer (*const c_void as c_ulonglong)0x0, 0x0000000012345678
{:o}%lloAsKPrintUnsignedInt (c_ulonglong)100
{:s}, &str, &[u8], &CStr%.*sAsKPrintStr (c_int, *const c_char)fuchsia
{:cs}, {:z}, *const c_char%sAsKPrintCString (*const c_char)null-terminated
{:c}, char, u8%.*sAsKPrintChar (c_int, *const c_char)Z, A, 🦀
{:b}, bool%.*slength (4 or 5), string pointertrue, false
{:.2f}, f64%.2fc_double3.14

Limitations & Intentional Differences from core::fmt

To avoid Rust-side formatting loops and intermediate stack buffers, kprint delegates all formatting to C printf / snprintf and has the following intentional limitations:

  • Byte-Based Width and Precision: For strings ({:s}) and characters ({:c}), field width and precision are measured in UTF-8 bytes (as understood by C printf's %.*s) rather than Unicode scalar values (chars).
  • Explicit Type Specifiers for Non-Literals: Because procedural macros run prior to Rust type checking, non-literal and non-cast expressions default to signed integer formatting (%lld) unless an explicit type specifier (:s, :u, :x, :X, :p, :c, :b, :f, :e, :E, :cs) is provided.
  • No Custom Fill Characters or Center Alignment: Only space and '0' padding with left (<) or right (>) alignment are supported by C printf. Specifying a custom fill character (such as {:_>8}) or center alignment ({:^8}) produces a compile-time error.
  • 64-Bit Integer Width: Integer specifiers convert arguments to c_longlong / c_ulonglong (64-bit).
  • Direct C printf Delegation for :o, :f, :e, :E, and :b:
    • {:b} formats bool values ("true" / "false") rather than base-2 integers.
    • {:o}, {:f}, {:e}, and {:E} map directly to C printf‘s %llo, %f, %e, and %E (e.g., {:.1e} formats the exponent using C’s 1.0e+03 notation).