Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

How could I implement the following example without using std?

let text = format!("example {:.1} test {:x} words {}", num1, num2, num3);

text has type &str and num1, num2 and num3 have any numeric type.

I've tried using numtoa and itoa/dtoa for displaying numbers but numtoa does not support floats and itoa does not support no_std. I feel like displaying a number in a string is fairly common and that I'm probably missing something obvious.

like image 505
chocol4te Avatar asked May 06 '18 13:05

chocol4te


5 Answers

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?
like image 50
Shepmaster Avatar answered Nov 20 '22 11:11

Shepmaster


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(())
    }
}

like image 43
Andelf Avatar answered Nov 20 '22 09:11

Andelf


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();
}
like image 31
Stefan Avatar answered Nov 20 '22 11:11

Stefan


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"
like image 44
Mika Vatanen Avatar answered Nov 20 '22 10:11

Mika Vatanen


I build a small crate based on Shepmaster's post. There is also a macro included that allows easy use. All this works without a heap allocator and is compatible with no_std.

use arrform::{arrform, ArrForm};
 
let af = arrform!(64, "write some stuff {}: {:.2}", "foo", 42.3456);
assert_eq!("write some stuff foo: 42.35", af.as_str());

This macro first reserves a buffer on the stack. Then it uses the struct ArrForm to format text and numbers. It returns an instance of ArrForm that allows easy access to the contained text. The macro panics if the buffer is chosen too small.

see https://github.com/Simsys/arrform or https://crates.io/crates/arrform.

like image 1
Simsys Avatar answered Nov 20 '22 10:11

Simsys