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

前端 未结 2 693
谎友^
谎友^ 2020-11-30 10:22

I want to do something like:

let x = 123;
let mut buf = [0 as u8; 20];
format_to!(x --> buf);
assert_eq!(&buf[..3], &b\"123\"[..]);
2条回答
  •  再見小時候
    2020-11-30 11:17

    Let's start with the standard version:

    use std::io::Write;
    
    fn main() {
        let x = 123;
        let mut buf = [0 as u8; 20];
        write!(&mut buf[..], "{}", x).expect("Can't write");
        assert_eq!(&buf[0..3], b"123");
    }
    

    If we then remove the standard library:

    #![feature(lang_items)]
    #![no_std]
    
    use core::panic::PanicInfo;
    
    #[lang = "eh_personality"]
    extern "C" fn eh_personality() {}
    
    #[panic_handler]
    fn panic(info: &PanicInfo) -> ! {
        loop {}
    }
    
    fn main() {
        let x = 123;
        let mut buf = [0 as u8; 20];
        write!(&mut buf[..], "{}", x).expect("Can't write");
        assert_eq!(&buf[0..3], b"123");
    }
    

    We get the error

    error[E0599]: no method named `write_fmt` found for type `&mut [u8]` in the current scope
      --> src/main.rs:17:5
       |
    17 |     write!(&mut buf[..], "{}", x).expect("Can't write");
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
    

    write_fmt is implemented in the core library by core::fmt::Write. If we implement it ourselves, we are able to pass that error:

    #![feature(lang_items)]
    #![feature(start)]
    #![no_std]
    
    use core::panic::PanicInfo;
    
    #[lang = "eh_personality"]
    extern "C" fn eh_personality() {}
    
    #[panic_handler]
    fn panic(info: &PanicInfo) -> ! {
        loop {}
    }
    
    use core::fmt::{self, Write};
    
    struct Wrapper<'a> {
        buf: &'a mut [u8],
        offset: usize,
    }
    
    impl<'a> Wrapper<'a> {
        fn new(buf: &'a mut [u8]) -> Self {
            Wrapper {
                buf: buf,
                offset: 0,
            }
        }
    }
    
    impl<'a> fmt::Write for Wrapper<'a> {
        fn write_str(&mut self, s: &str) -> fmt::Result {
            let bytes = s.as_bytes();
    
            // Skip over already-copied data
            let remainder = &mut self.buf[self.offset..];
            // Check if there is space remaining (return error instead of panicking)
            if remainder.len() < bytes.len() { return Err(core::fmt::Error); }
            // Make the two slices the same length
            let remainder = &mut remainder[..bytes.len()];
            // Copy
            remainder.copy_from_slice(bytes);
    
            // Update offset to avoid overwriting
            self.offset += bytes.len();
    
            Ok(())
        }
    }
    
    #[start]
    fn start(_argc: isize, _argv: *const *const u8) -> isize {
        let x = 123;
        let mut buf = [0 as u8; 20];
        write!(Wrapper::new(&mut buf), "{}", x).expect("Can't write");
        assert_eq!(&buf[0..3], b"123");
        0
    }
    

    Note that we are duplicating the behavior of io::Cursor into this wrapper. Normally, multiple writes to a &mut [u8] will overwrite each other. This is good for reusing allocation, but not useful when you have consecutive writes of the same data.

    Then it's just a matter of writing a macro if you want to.

    You should also be able to use a crate like arrayvec, which has written this code for you. This is untested:

    #![feature(lang_items)]
    #![feature(start)]
    #![no_std]
    
    use core::panic::PanicInfo;
    
    #[lang = "eh_personality"]
    extern "C" fn eh_personality() {}
    
    #[panic_handler]
    fn panic(_info: &PanicInfo) -> ! {
        loop {}
    }
    
    use arrayvec::ArrayString; // 0.4.10
    use core::fmt::Write;
    
    #[start]
    fn start(_argc: isize, _argv: *const *const u8) -> isize {
        let x = 123;
        let mut buf = ArrayString::<[u8; 20]>::new();
        write!(&mut buf, "{}", x).expect("Can't write");
        assert_eq!(&buf, "123");
        0
    }
    

提交回复
热议问题