Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.8k views
in Technique[技术] by (71.8m points)

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

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"[..]);

With #![no_std] and without any memory allocator.

As I understand, there is an implementation of core::fmt::Display for u64, and I want to use it if possible.

In other words, I want to do something like format!(...), but without a memory allocator. How can I do this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...