1

I'm currently learning Rust's lifetime and ownership. I encountered codes like this(minimized):

fn main() {
    let mut text = Text::new();
    println!("text: {:?}", text);

    let mut byte = [1_u8];
    let txt = text.get_text(byte);
    println!("byte: {:?}", byte);
    println!("txt: {:?}", txt);

    byte[0] = 7_u8;
    println!("byte: {:?}", byte);
    println!("txt: {:?}", txt);
}

#[derive(Debug)]
struct Text<'a> {
    temp: [u8; 1],
    data: &'a [u8],
}

impl<'a> Text<'a> {

    fn new() -> Self {
        Text {
            temp: [0_u8],
            data: &[0],
        }
    }

    fn get_lifetime(&self) -> &'a Self {
        unsafe { 
            // &*(self as *const Self) 
            std::mem::transmute(self)
        }
    }

    fn get_text(&mut self, byte: [u8; 1]) -> Text<'a> {
        self.temp = byte;
        let res = Text {
            temp: [0_u8],
            data: &self.get_lifetime().temp,
        };
        res
    }
}

the output is

text: Text { temp: [0], data: [0] }
byte: [1]
txt: Text { temp: [0], data: [1] }
byte: [7]
txt: Text { temp: [0], data: [1] }

It seems that the slice data inside variable text point to some place independent of byte. I think maybe the byte is copied into get_text. But is slice data always refer to the copied one? How does Rust determine when to drop the copied byte?

Further more, I'm confused by the use of get_lifetime. Can anyone further explain the usage, and whether it is safe to use?

Comcx
  • 55
  • 5
  • 1
    It seems you're trying to build a self-referential struct. This is impossible in safe Rust and hard to get soundly in general. See this question for example - https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct – Cerberus Oct 29 '22 at 10:46
  • 1
    @Cerberus But I think the `temp` field and `data` field point to different memory. Am I accidentally create self reference struct? – Comcx Oct 29 '22 at 10:54

1 Answers1

1

It seems that the slice data inside variable text point to some place independent of byte.

The value of data is initially set to &[0]. A slice literal is 'static and the reference is valid for the entire duration of the program.

After you call get_text, the value of byte is copied into temp. Then data is made into a reference to that field.

This is not safe. In the code you've actually shown, there is no problem. The problem is how this struct might be used later.

Consider this example:

fn main() {
    let mut text = Text::new();
    let mut byte = [1_u8];
    let txt = text.get_text(byte);
    println!("txt: {:?}", txt); // txt: Text { temp: [0], data: [1] }
    use_text(text);
}

// Don't inline, so it's guaranteed that text is physically moved in memory
#[inline(never)]
fn use_text<'a>(text: Text<'a>) {
    println!("txt: {:?}", text); // Undefined Behaviour
}

In this example text is moved into the use_text function's call frame. That means it is stored in a new location in memory. But data will still point to to the old place in memory where the temp field used to be stored.

This illustrates one of the big dangers of using unsafe. The consequences may be observed far away from where you used that keyword. This example was sound until text was moved, but after that point, it is Undefined Behaviour.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Thanks for your great answer! I also have one question: If `text` is not moved and I change its inner `temp`, is it guaranteed that `txt`'s data will point to the changed value? – Comcx Oct 29 '22 at 11:32