/printf-compat

printf reimplemented in Rust

Primary LanguageRustApache License 2.0Apache-2.0

printf-compat

Crates.io Docs.rs

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::fmt too big? No problem! Write your own formatting code, or use a minimal formatting library like ufmt or defmt. Don't need every single option given by printf format strings? No problem! Just don't implement it.
  • Likewise, if you're using wasm32-unknown-unknown instead 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 for printf.

Benefits

⚒ Modular

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.

🔬 Small

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.

🔒 Safe (as can be)

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.

🧹 Tested

A wide test suite is used to ensure that many different possibilities are identical to glibc's printf. Differences are documented.

Getting Started

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 printf because you don't have one, you'll want to call it printf and 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_written

Of 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