Formatting
Flint provides two formatting packages for different scenarios:
| Package | Allocation | Return type | When to use |
|---|---|---|---|
core/fmt | None | Result<(), Error> source API | No-heap checked formatting for sink-style output |
std/fmt | May allocate | Result<string, Error> | Planned owned-string formatting surface |
Current Status
core/fmt.write is available today, but the current executable backend slice is narrower than the long-term library design:
- The compiler recognizes
fmt.write(...)as a checked formatting call. - The format string must be a compile-time string literal.
- The compiler handles the variable-argument source shape specially so ordinary variadics are not required in the language.
- The first argument must be a concrete sink type that implements
core/io.Writer. - The current executable backend slice resolves the concrete
Writerimplementation for that sink and lowers the supported sink path. std/fmt.format(...)is reserved in the language and docs, but it is not implemented end to end yet.- General runtime trait-dispatch through a trait-typed parameter such as
mut out: io.Writeris still separate backend work.
core/fmt.write: No-Heap Checked Formatting
use board/pico as board
use core/fmt
fn main() -> never {
let uart = board.default_uart().init()
let value: u32 = 42
fmt.write(uart, "value={}\r\n", value)
loop {}
}
Today, the first argument should be a concrete sink handle such as a UART port whose type implements core/io.Writer. The format string must be a compile-time string literal, and the compiler validates it at build time.
core/fmt.write is the preferred formatting path for MCU code because it never allocates.
std/fmt.format: Owned String Formatting
use std/fmt
let msg = std_fmt.format("rx={} tx={}", rx_count, tx_count)?
send_log(msg)
This is the intended source API because building a string may allocate. It is not implemented end to end yet on the current toolchain.
Tip: Use
use std/fmt as std_fmtto avoid shadowingcore/fmtif you import both.
Format String Syntax
Format strings use a minimal placeholder language:
| Placeholder | Meaning |
|---|---|
{} | Format next argument in default style |
{{ | Literal { |
}} | Literal } |
Placeholder count must match argument count exactly; a mismatch is a compile error:
fmt.write(out, "x={} y={}", x, y) // ok
fmt.write(out, "x={}", x, y) // error: too many arguments
fmt.write(out, "x={} y={}", x) // error: too few arguments
Supported Argument Types
The current checked-format type checker accepts:
- Unit
() - Integers (
u8,u16,u32,usize,i8,i16,i32,isize) bool: formats astrueorfalsef32: decimal representationchar: the character itselfstring: the string valueError: the error message
On the current executable path, the implemented argument slice is unit, booleans, integers, f32, char, string literals, and string locals. User-defined display/debug traits are not supported yet. For custom types, format the fields explicitly.
Backend Notes
Current backends may still use compiler-aware lowering behind fmt.write(...), but the sink contract is now the concrete core/io.Writer implementation selected for the first argument. Future targets may use a different backend strategy, including compact encoded records similar to defmt, behind the same source API.
Example: Status Line Over UART
use board/pico as board
use core/fmt
use std/time
fn main() -> never {
let port = board.default_uart().init()
let mut tick: u32 = 0
loop {
fmt.write(port, "[{}] alive\r\n", tick)
tick += 1
time.sleep_ms(1000)
}
}