Search code examples
rust

How to write_all to a slice in Rust many times and get output


use std::io::Write;

pub struct A{
    pub a: u8,
    pub b: u16
}

impl A {
    pub fn to_bytes(&self, output: &mut &mut[u8]) -> Result<(), std::io::Error> {
        output.write_all(&[self.a])?;
        output.write_all(&self.b.to_be_bytes()[..])?;
        Ok(())
    }
}

fn main() {
    let mut buffer: &mut [u8] = &mut [0u8; 10];
    let a = A{
        a: 7,
        b: 100
    };
    a.to_bytes(&mut buffer);
    println!("{:?}", buffer);
}

this prints [0, 0, 0, 0, 0] because it splits the slice and gives the rest for more writes. playground

What would be the most elegant way to get the actual output after many writes, and not the remainder slice? I don't want to keep track of how many bytes were written just so I can subslice the buffer output.

Context: microcontrollers don't allow for memory allocation so I pass a worst case buffer slice for write and it's almost never filled to the end.


Solution

  • Mutate a reborrow so that buffer keeps the original range:

    impl A {
        pub fn to_bytes(&self, mut output: &mut[u8]) -> Result<(), std::io::Error> {
            ...             // ^^^
        }
    }
    
    fn main() {
        ...
        a.to_bytes(buffer);
        ...
    }
    

    It will print:

    [7, 0, 100, 0, 0, 0, 0, 0, 0, 0]
    

    playground link


    If you want to write the data, keep track of how many bytes were written, and use that to affect buffer in-place; you can accomplish that like so:

    impl A {
        pub fn to_bytes(&self, buffer: &mut &mut[u8]) -> Result<(), std::io::Error> {
            let mut output = &mut **buffer;
            
            let space = output.len();
            
            output.write_all(&[self.a])?;
            output.write_all(&self.b.to_be_bytes()[..])?;
            
            let written = space - output.len();
            
            // see https://stackoverflow.com/questions/61223234/can-i-reassign-a-mutable-slice-reference-to-a-sub-slice-of-itself
            // for in-place mutable sub-slicing
            let orig = std::mem::replace(buffer, &mut []);
            *buffer = &mut orig[..written];
            
            Ok(())
        }
    }
    

    with your code will print:

    [7, 0, 100]
    

    playground link

    It would likely be more extensible and ergonomic if you just returned the bytes written and slice yourself, but to each their own.