Most elegant way to 'write' to slice without stdlib (possibly on microcontrollers)

Suppose you have a struct

struct A {
    a: u8,
    b: u16,
    c: u32
}

and you want to output it to a slice like this

impl A {
    pub fn to_buffer(&self, output: &mut [u8]) -> Result<usize, Error>{
        todo!()
    }
}

but it's gonna run into a microcontroller, so you can't use the std lib and possibly other features. I've been doing like this:

 pub fn to_buffer(&self, output: &mut [u8]) -> Result<usize, Error>{
       output[0] = self.a;
       output[1..3].copy_from_slice(self.b.to_le_bytes());
       output[3..7].copy_from_slice(self.c.to_le_bytes());
       7
    }

the problem is that this is error prone and also could panic if the slice has not enough space. It's much better if I could do something like

output.write(self.a)?; 
output.write(self.b)?;
output.write(self.c)?

I can do this with a Cursor but I want to do it without the std lib. Also, I need to return the ammount of bytes written, because the output buffer is possibly pre-allocated into the ROM so it has a much bigger size then needed (worst case size).

What would be the most elegant way and less error prone?

To avoid a panic, you can do output.get_mut(i..j).ok_or_else(|| Error::new(..))? instead of output[i..j].

Do you need to return the amount of bytes written even if it encounters an Err(_)? The way your function signature is currently written, the amount of bytes are only reported for the Ok(_) case.

Depending on what you are really trying to accomplish here, you may also consider implementing a serializer with serde.

serde works without core and/or stdlib?

Yes, it does work without std or alloc … well, no, not without core – who uses Rust without core?

If you don’t mind the exact binary representation, you can also just use an existing one. E.g. postcard. (Note that they did e.g. choose variable-length integer encoding as a default.)

1 Like

If you want to serialize a specific format of data, like to match a communication protocol or storage format, then I'd suggest the binrw crate. It's a pleasure to work with for these kind of jobs.

And it works great in no_std environments. Example

#![no_std]
#![no_main]

#[binrw]
#[brw(little)]
struct A {
    a: u8,
    b: u16,
    c: u32,
}

#[no_mangle]
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
    let value1 = A {
        a: 0x11,
        b: 0x2233,
        c: 0x44556677,
    };
    let mut buf = [0u8; 16];
    let mut cursor = binrw::io::Cursor::new(&mut buf[..]);
    value1.write(&mut cursor).unwrap();

    unsafe {
        libc::printf(
            "wrote %u bytes:\0".as_ptr() as *const _,
            cursor.position() as libc::c_uint,
        );
    }

    let n = cursor.position() as usize;
    let buf = cursor.into_inner();
    for &b in &buf[0..n] {
        unsafe {
            libc::printf(" %02X\0".as_ptr() as *const _, b as libc::c_uint);
        }
    }

    unsafe {
        libc::printf("\n\0".as_ptr() as *const _);
    }

    0
}

Output:

wrote 7 bytes: 11 33 22 77 66 55 44
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.