How can I use the format! macro in a no_std environment?

In addition to Shepmaster's answer you can also format strings without an allocator.

In core::fmt::Write you only need to implement write_str and then you get write_fmt for free.

With format_args!(...) (same syntax as format!) you can prepare a core::fmt::Arguments value, which can be passed to core::fmt::write.

See Playground:

#![crate_type = "dylib"]
#![no_std]

pub mod write_to {
    use core::cmp::min;
    use core::fmt;

    pub struct WriteTo<'a> {
        buffer: &'a mut [u8],
        // on write error (i.e. not enough space in buffer) this grows beyond
        // `buffer.len()`.
        used: usize,
    }

    impl<'a> WriteTo<'a> {
        pub fn new(buffer: &'a mut [u8]) -> Self {
            WriteTo { buffer, used: 0 }
        }

        pub fn as_str(self) -> Option<&'a str> {
            if self.used <= self.buffer.len() {
                // only successful concats of str - must be a valid str.
                use core::str::from_utf8_unchecked;
                Some(unsafe { from_utf8_unchecked(&self.buffer[..self.used]) })
            } else {
                None
            }
        }
    }

    impl<'a> fmt::Write for WriteTo<'a> {
        fn write_str(&mut self, s: &str) -> fmt::Result {
            if self.used > self.buffer.len() {
                return Err(fmt::Error);
            }
            let remaining_buf = &mut self.buffer[self.used..];
            let raw_s = s.as_bytes();
            let write_num = min(raw_s.len(), remaining_buf.len());
            remaining_buf[..write_num].copy_from_slice(&raw_s[..write_num]);
            self.used += raw_s.len();
            if write_num < raw_s.len() {
                Err(fmt::Error)
            } else {
                Ok(())
            }
        }
    }

    pub fn show<'a>(buffer: &'a mut [u8], args: fmt::Arguments) -> Result<&'a str, fmt::Error> {
        let mut w = WriteTo::new(buffer);
        fmt::write(&mut w, args)?;
        w.as_str().ok_or(fmt::Error)
    }
}

pub fn test() {
    let mut buf = [0u8; 64];
    let _s: &str = write_to::show(
        &mut buf,
        format_args!("write some stuff {:?}: {}", "foo", 42),
    ).unwrap();
}

You can also combine the usage of numtoa and arrayvec crates. Example:

#![no_std]

use numtoa::NumToA;
use arrayvec::ArrayString;

fn main() -> ! {
    let mut num_buffer = [0u8; 20];
    let mut text = ArrayString::<[_; 100]>::new();

    let num1 = 123;
    let num2 = 456;
    let num3 = 789;

    // text.clear(); (on subsequent usages)
    text.push_str("example ");
    text.push_str(num1.numtoa_str(10, &mut num_buffer));

    text.push_str(" test ");
    text.push_str(num2.numtoa_str(10, &mut num_buffer));

    text.push_str(" words ");
    text.push_str(num3.numtoa_str(10, &mut num_buffer));
}

Note that push_str can panic. Check out the api for try_ -methods

And Cargo.toml

 [dependencies]
 arrayvec = { version = "0.5", default-features = false }
 numtoa = "0.2"

Write a formatter!

use core::fmt::{self, Write};
use core::str;

fn main() {
    // For LCD 160 / 8 = 20 chars
    let mut buf = [0u8; 20];
    let mut buf = ByteMutWriter::new(&mut buf[..]);
  
    buf.clear();
    write!(&mut buf, "Hello {}!", "Rust").unwrap();
    
    // buf.as_str()
}


pub struct ByteMutWriter<'a> {
    buf: &'a mut [u8],
    cursor: usize,
}

impl<'a> ByteMutWriter<'a> {
    pub fn new(buf: &'a mut [u8]) -> Self {
        ByteMutWriter { buf, cursor: 0 }
    }

    pub fn as_str(&self) -> &str {
        str::from_utf8(&self.buf[0..self.cursor]).unwrap()
    }

    #[inline]
    pub fn capacity(&self) -> usize {
        self.buf.len()
    }

    pub fn clear(&mut self) {
        self.cursor = 0;
    }

    pub fn len(&self) -> usize {
        self.cursor
    }

    pub fn empty(&self) -> bool {
        self.cursor == 0
    }

    pub fn full(&self) -> bool {
        self.capacity() == self.cursor
    }
}

impl fmt::Write for ByteMutWriter<'_> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let cap = self.capacity();
        for (i, &b) in self.buf[self.cursor..cap]
            .iter_mut()
            .zip(s.as_bytes().iter())
        {
            *i = b;
        }
        self.cursor = usize::min(cap, self.cursor + s.as_bytes().len());
        Ok(())
    }
}


In general, you don't. format! allocates a String, and a no_std environment doesn't have an allocator.

If you do have an allocator, you can use the alloc crate. This crate contains the format! macro.

#![crate_type = "dylib"]
#![no_std]

#[macro_use]
extern crate alloc;

fn thing() {
    let text = format!("example {:.1} test {:x} words {}", 1, 2, 3);
}

See also:

  • How to format output to a byte array with no_std and no allocator?