Question about ownership, slices and lifetimes

Hi!
First of all sorry if this is a recurring question, I'm still in the early phases of learning Rust and this is very confusing to me.
I've been trying to interpret a slice of a Vec<u8> as a string (an AsciiStr, to be exact, but it should behave similarly to a regular str). Here is a snippet :

struct Rom {
    data: Vec<u8>,
}

impl Rom {
    fn new(filename: &str) -> Rom {
        // populates data
    }
    fn name(self) -> &AsciiStr {
        self.data[0xA0..0xAB].as_ascii_string().unwrap()
    }
}

In this state, this snippet doesn't compile. Here's what I'm not sure about :

  • taking a slice of self.data takes ownership, so I won't be able to reaccess self.data in some other place after that. Can I make name() return a "view" to the data, reinterpreted as an AsciiStr, but still be able to read and modify self.data afterwards ?
  • name() returns an &AsciiStr. From what I gather about lifetimes, I should annotate it so this AsciiStr exists as long as data exists, but I'm very confused as to how to do that.
  • What if I wanted to copy some bytes of data and reinterpret that as an AsciiStr ? What would be the best way to do it ?

Any pointers or help would be appreciated. Thanks !

In this snippet:

fn name(self) -> &AsciiStr {
    self.data[0xA0..0xAB].as_ascii_string().unwrap()
}

what happens is that due to using self instead of &self, calling this function takes ownership of the Rom. The as_ascii_string will then proceed to borrow from the slice self.data[0xA0..0xAB] and return this borrow.

Had you use &self, this would be perfectly fine, but since you took ownership of self, this means that the Rom and therefore the vector is dropped at the end of the name function, deallocating the memory and invalidating the slice.

So you should change self to &self.

The error message is

error[E0106]: missing lifetime specifier
 --> src/lib.rs:9:22
  |
9 |     fn name(self) -> &AsciiStr {
  |                      ^ help: consider giving it a 'static lifetime: `&'static`
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

In fact the error message informs you that there is nothing for the AsciiStr to borrow from. The suggestion to add a 'static is not what you want here, instead you want to allow it to borrow from the Rom.

6 Likes

Exactly ! Thank you for the explanation.
As for how to copy data instead of simply referencing it, I've written this:

fn name_copied(&self) -> AsciiString {
    let mut buf = vec![0u8; 12];
    buf.clone_from_slice(&self.data[0xA0..0xA0 + 12]);
    AsciiString::from_ascii(buf).unwrap()
} 

This works using an AsciiString instead of an &AsciiStr, in order to return an owned value instead of a reference. This should also be analogous to the relationship beween String and &str.

It works, but I wonder if there is a way to skip initializing buf with zeroes, given that it will be overwritten by clone_from_slice anyway.

Sure!

fn name_copied(&self) -> AsciiString {
    let mut buf = Vec::with_capacity(12);
    buf.extend(&self.data[0xA0..0xA0 + 12]);
    AsciiString::from_ascii(buf).unwrap()
}
2 Likes

And even shorter, as I assume AsciiString::from_ascii copies bytes:

fn name_copied(&self) -> AsciiString {
    AsciiString::from_ascii(&self.data[0xA0..0xA0 + 12]).unwrap()
}
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.