What is the relationship between the lifetime in struct definition and that of associated function

struct Data<'a>{
   v:&'a i32
}
impl<'a> Data<'a>{
   fn get_value_ref<'b>(&'b self)-> &'b i32{
      self.v
  }
}
fn main(){
   let i = 0;
   let d = Data{v:&i};
   let rf = d.get_value_ref();
}

What is the relationship between 'a and 'b in this case? Does any rule in rust reference say that 'a outlives 'b? In other words, is there any implicit trait bound like where 'a:'b in the implementation definition?

Yes. The type of self in get_value_ref is &'b Data<'a>, and any time you mention a type like that, it automatically inserts a bound that 'b is smaller than 'a.

2 Likes

A formal definition about this concept is appreciated.

Are you looking for something like this?

If a type T is annotated with a lifetime 'a, then values of type T may never leave the lifetime 'a. (To be more precise, the lifetime 'a corresponds to a region of code, and the value may never leave that region.)

Whenever a reference &'a T is created, the compiler ensures that T remains valid (doesn't move and isn't modified and so on) for the entire duration of 'a.

Thus, if you have a &'b Data<'a>, then you know that there exists a Data<'a> somewhere and that the Data<'a> remains valid for the entire duration of 'b. Furthermore, since Data<'a> is annotated with 'a, you know that Data<'a> is only valid inside 'a. It follows that 'b must be contained in 'a.

Thus, values of type &'b Data<'a> can only exist if 'b is contained in 'a.

3 Likes

Acutall, the issue arise from this example

struct FileReader {
    file: std::fs::File,
}
struct FileReaderIndice<'a>{
	index:std::ops::Range<usize>,
	buffer:Vec<u8>,
	result:std::io::Result<Option<&'a [u8]>>
}
impl<'a,'b> Index<&'b mut FileReaderIndice<'a>> for FileReader {
    type Output = std::io::Result<Option<&'a [u8]>>;
    fn index(& self, index: &'b mut FileReaderIndice<'a>) -> & Self::Output {
        unimplemented!()
    }
}

impl<'a,'b:'a> IndexMut<&'b mut FileReaderIndice<'a>> for FileReader {
    fn index_mut(& mut self, index: &'b mut FileReaderIndice<'a>) -> & mut Self::Output {
         // reading something into index.buffer
        index.result = Ok(Some(&index.buffer[0..]));
        & mut index.result
    }
}

If we remove the trait bound 'b:'a, the compiler would friendly remind that:

lifetime may not live long enough
consider adding the following bound: `'b: 'a`

I just want to know the reason, consider the associated method would be like

 fn index_mut<'c>(&'c mut self, index: &'b mut FileReaderIndice<'a>) -> &'c mut Self::Output 

The signature of the method in the IndexMut trait seems to admit the lifetime of the return type have the same lifetime of the first argument, so, I annoted them as 'c. According to your answers, since the Self::Output is std::io::Result<Option<&'a [u8]>>, hence the return type is &'c mut std::io::Result<Option<&'a [u8]>>, hence 'a lives at least as long as 'c right? In this case, the return value is valid if only the lifetime 'b lives at least long enough as 'a, such that they have the relationship

'b ≥ 'a ≥ 'c

Is this the reason why the friendly remind say "'b:'a"?

It sounds like the issue is that you're trying to make a self-referential struct. I.e., a struct where one field has a reference to another field of the same struct. Those aren't possible in safe Rust.

3 Likes

The above code is valid in safe rust. The example just can be compiled. The simplified example is

struct Data<'a>{
    buffer:Vec<u8>,
    slice:Option<&'a [u8]>
}
fn main(){
    let mut d = Data{buffer:vec![b'1',b'2',b'3'],slice:None};
    d.slice = Some(&d.buffer[0..]);
}

playground. This example is valid. However, as a contrast, the following example is invalid

struct Data<'a>{
    buffer:Vec<u8>,
    self_referential:Option<&'a Data<'a>>
}
fn main(){
    let mut d = Data{buffer:Vec::new(),self_referential:None};
    d.self_referential = Some(&d);  // error
}

I don't know why the second example is invalid.

Both are hopelessly broken, it's just that in the first example you don't see how unusable it is, because you haven't tried to use it (the struct remains borrowed, immutable, and unmovable).

This approach is a dead-end and won't work. Rust's references are not a general-purpose way of merely referencing data. They are temporary loans that place heavy restrictions on the object they're borrowing from, and there is no amount of lifetime annotations that would make them less restrictive.

This type of self-reference is not safe in Rust's borrow-checking model. If the code compiled, it couldn't stop you from calling buffer.clear() that would make all other loans dangling. Other languages don't have this restriction thanks to garbage collection, but Rust has no way of tracking outstanding references without also "freezing" their target.

You may need to split the buffer and the slice into two objects, like Vec and IterMut are split. Or use VecDeque. Or track the position using integers. It needs a different design.

...but it breaks if you attempt to move the struct:

struct Data<'a>{
    buffer:Vec<u8>,
    slice:Option<&'a [u8]>
}
fn main(){
    let mut d = Data{buffer:vec![b'1',b'2',b'3'],slice:None};
    d.slice = Some(&d.buffer[0..]);
    let d = d; // error[E0505]: cannot move out of `d` because it is borrowed
}

Self-referential structs implicitly impose the requirement that the struct is borrowed for the same duration as the lifetime it is annotated with. This implies that the struct remains borrowed until it is destroyed. In the example you posted, this happens to be OK, but in real code, this requirement will generally turn out to be a problem.

For example, you can never move or modify the struct once you make it self-referential, because either operation requires the borrow to end before the struct is destroyed.

So, Does it mean we cannot have a self-referential struct in Rust anyway?

Generally, no.

There are 3rd party crates that hack around it with various level of success. Self-referential structs happen to exist in Rust internally in Future objects created by async functions and blocks, but there isn't any language feature to safely create custom self-referential structs with temporary loans.

You can make self-referential structs with "raw" C-like pointers, but safety of that isn't checked.

Not sure whether my understanding of this example is correct, IMO

 let mut d = Data{buffer:vec![b'1',b'2',b'3'],slice:None};
 d.slice = Some(&d.buffer[0..]);  // #1 start of lifetime 'a
 let d = d; // #2 end of lifetime 'a, however, `d` is moved here

According to NLL, the lifetime borrowed content should live at least as long as from #1 to #2, however, the content is moved at #2. Is this the reason why the compiler emits an error?

Consider this struct, which has lifetimes identical to your case:

struct Data<'a>{
    buffer:[u8; 100],
    slice:Option<&'a [u8]>
}

A code like:

d.slice = Some(&d.buffer[0..])
let d = d;

The move in let can memcpy the struct to a new address. From language perspective all moves are the same, whether that's let d = d or let d = Box::new(d) or drop(d) — it's moved and no longer exists where it used to be.

But because this time the buffer happens to be in the struct without a level of indirection, you should see that this operation creates a dangling pointer in d.slice. At that point accessing d.slice would read no-longer-valid memory.

Lifetimes don't care if they point to the heap or the stack, so borrowing from buffer: Vec<u8> is like borrowing from buffer: [u8; 100], and gives you the same kind of slice with the same kind of a lifetime. Borrow checker operates on theoretical rules, sort of like chess. It doesn't matter how you can move pieces in the physical world, only what moves the rules say are possible.

1 Like

Let me give the variables new names:

 let mut d = Data{buffer:vec![b'1',b'2',b'3'],slice:None};
 d.slice = Some(&d.buffer[0..]);  // #1 start of lifetime 'a
 let d2 = d; // #2 end of lifetime 'a, however, `d` is moved here

Both d and d2 have the same type of Data<'a>, so d2 is annotated with the lifetime 'a. This implies that the lifetime 'a must end no earlier than when d2 goes out of scope.

Furthermore, when you type d.slice = Some(&d.buffer[0..]), this involves the requirement that &d.buffer[0..] has the type &'a [u8]. However, the only way to create a &'a [u8] that points into d.buffer is to borrow d.buffer for a duration that is at least the lifetime 'a.

But we now have that d.buffer is borrowed for a duration that is at least the lifetime 'a, which lasts at least until d2 goes out of scope. Thus, d.buffer is borrowed at least until when d2 goes out of scope. This is not allowed because d gets moved before d2 goes out of scope.

NLL is not relevant to this analysis at all.

2 Likes

I think nomicon is more than explicit:

Move constructors are meaningless in Rust because we don't enable types to "care" about their location in memory. Every type must be ready for it to be blindly memcopied to somewhere else in memory. This means pure on-the-stack-but- still-movable intrusive linked lists are simply not happening in Rust (safely).

I don't know how more explicit can you get.

Consider such two examples

struct Data<'a>{
    slice:Option<&'a [u8]>
}
fn main() {
   let mut d = Data{slice:None};
   let buff = vec![1u8,2u8,3u8];
   d.slice = Some(&buff[0..]);  // #1
   let move_to  = buff;  // the borrowed content is moved
}

This example is just ok. I think NLL does apply to this case(i.e. the variable d is no longer used after #1). As a contrast, the following example is wrong

fn main() {
   let mut d = Data{slice:None};
   let buff = vec![1u8,2u8,3u8];
   d.slice = Some(&buff[0..]);  // #1
   let move_to  = buff;  // the borrowed content is moved
   let r = d.slice.clone();  // #2
}

The variable d is used at #2 but the borrowed content buff has been moved out prior to #2.

NLL does enable the first example and the second example cannot be allowed, yes. These new examples are no longer self-referential, so you can borrow buff for longer than you exclusively borrow d in order to assign a new value to d.slice.

But the point still stands that NLL doesn't change the analysis of the self-referential case: You have to exclusively borrow the container for as long as you borrow the contained field, which results in borrowing the container for the rest of it's validity. Whether that borrow is extended to the end of a scope or not is irrelevant.

Incidentally, if you want to see how the lexical borrow checker would behave, you can try compiling code using an older version of the compiler.

I think I have two confusions here. For the lifetime annoation, such as

&'a X

What does it mean? Does it mean the borrowed content X should outlive 'a or mean the reference outlive 'a?

For the quoted wording in you answer. Does it mean borrow to a contained field is equivalent to as if we borrowed the containing struct? which result in we cannot borrow the container as it would have violated the reference rules as if we have had borrowed the container?

That means "reference must be allowed to live for 'a" (but can be dropped earlier). From this, for type to be well-formed, follows "X must be alive for at least 'a".