In Rust, is it possible to have a mutable copy-able string allocated on the stack?

Viewed 137

I need something that will make this work:

#[derive(Copy)]
struct TestData {
    val : MutableString
}

Presumably, the MutableString will have to be of fixed size, and I am okay with that.

1 Answers

We can use const generics for this!

#[derive(Copy, Clone)]
struct TestData<const SIZE1: usize> {
    val : MutableString<SIZE1>
}

#[derive(Copy, Clone)]
struct MutableString<const SIZE: usize> {
    inner : [u8; SIZE],
    len: usize
}

(note: these are both Copy, but Clone must also be present due to trait bounds)

The SIZE parameter must be a value that is computable at compile time so you are right about needing fixed length strings.

We need a len field here because we need to know how long the actual string itself is. The alternative would be using a null-terminator like C does.

I defined a couple methods to make one of these from an &str and turn one back into a &str:

impl<'a, const SIZE: usize> MutableString<SIZE> {
    fn from_str(s: &str) -> Result<Self, &'static str> {
        let slice = s.as_bytes();
        if slice.len() > SIZE {
            Err("String is too big")
        } else {
            let mut inner = [0; SIZE];
            for (i, byte) in slice.iter().enumerate() { inner[i] = *byte; }
            Ok(Self {
                inner,
                len: slice.len()
            })
        }
    }

    fn to_str(&'a self) -> &'a str {
        std::str::from_utf8(&self.inner).unwrap()
    }
}

In the real world you'd want more error handling around this and also offer methods that guard mutation to ensure it doesn't expand beyond SIZE or require len to be updated.

We can verify that the string is being copied:

fn main() {
    let mut_str = MutableString::from_str("Hello, world!").unwrap();
    let mut data: TestData<256> = TestData { val: mut_str };
    let copy = data;

    data.val.inner[12] = b'?'; // irl guard this around a method that updates len, etc.

    println!("{}", data.val.to_str());
    println!("{}", copy.val.to_str());
}
Related