printf reimplemented in Rust
This is a complete reimplementation of printf in Rust, using the unstable
(i.e. requires a Nightly compiler) c_variadic feature.
- Many C libraries provide a way to provide a custom log callback. With this crate, you can provide a pure Rust option, and do whatever you want with it. Log it to the console, store it in a string, or do anything else.
- If you're writing a Rust-first program for a microcontroller and need to
interface with a C library, you might not have a libc and have to
reimplement it yourself. If it uses
printf, use this crate to easily add your own output.core::fmttoo big? No problem! Write your own formatting code, or use a minimal formatting library likeufmtordefmt. Don't need every single option given byprintfformat strings? No problem! Just don't implement it. - Likewise, if you're using
wasm32-unknown-unknowninstead of emscripten (as wasm-bindgen is only compatible with the former), you have no libc. If you want to interface with a C library, you'll have to do it all yourself. With this crate, that turns into 5 lines instead of hundreds forprintf.
printf-compat lets you pick how you want to output a message. Use
pre-written adapters for fmt::Write (like a
String) or io::Write (like
io::stdout()), or implement your own.
This crate is no_std compatible (printf-compat = { version = "0.1", default-features = false } in your Cargo.toml). The main machinery doesn't
require the use of core::fmt, and it can't panic.
Of course, printf is completely unsafe, as it requires the use of
va_list. However, outside of that, all of the actual string parsing is
written in completely safe Rust. No buffer overflow attacks!
The n format specifier, which writes to a user-provided pointer, is
considered a serious security vulnerability if a user-provided string is
ever passed to printf. It is supported by this crate; however, it
doesn't do anything by default, and you'll have to explicitly do the writing
yourself.
A wide test suite is used to ensure that many different possibilities are
identical to glibc's printf. Differences are
documented.
Start by adding the unstable feature:
#![feature(c_variadic)]Now, add your function signature:
use core::ffi::{c_char, c_int};
#[no_mangle]
unsafe extern "C" fn c_library_print(str: *const c_char, mut args: ...) -> c_int {
todo!()
}Think about what you're doing:
- If you're implenting
printfbecause you don't have one, you'll want to call itprintfand add#[no_mangle]. - Likewise, if you're creating a custom log function for a C library and it
expects to call a globally-defined function, keep
#[no_mangle]and rename the function to what it expects. - On the other hand, if your C library expects you to call a function to
register a callback (example 1, example 2),
remove
#[no_mangle].
Now, add your logic:
use printf_compat::{format, output};
let mut s = String::new();
let bytes_written = format(str, args.as_va_list(), output::fmt_write(&mut s));
println!("{}", s);
bytes_writtenOf course, replace output::fmt_write with whatever you like—some are
provided for you in output. If you'd like to write your own, follow
their function signature: you need to provide a function to format()
that takes an Argument and returns the number of bytes written (although
you don't need to if your C library doesn't use it) or -1 if there was an
error.
License: MIT OR Apache-2.0