Convert a struct with mutable reference to another struct with immutable one

I have something like this

struct WithMutRef<'a, T> {
    r: &'a mut T,
}

struct WithRef<'a, T> {
    r: &'a T,
}

impl<'a, T> From<&WithMutRef<'a, T>> for WithRef<'a, T> {
    fn from(src: &WithMutRef<'a, T>) -> WithRef<'a, T> {
        WithRef{r: src.r}
    }
}

impl<'a, T> From<&mut WithMutRef<'a, T>> for WithRef<'a, T> {
    fn from(src: &mut WithMutRef<'a, T>) -> WithRef<'a, T> {
        WithRef{r: src.r}
    }
}

which gives me an error

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/lib.rs:11:9
   |
11 |         WithRef{r: src.r}
   |         ^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined here...

It compiles when converted to this

impl<'a, T> From<&'a WithMutRef<'a, T>> for WithRef<'a, T> {
    fn from(src: &'a WithMutRef<'a, T>) -> WithRef<'a, T> {
        WithRef{r: src.r}
    }
}

But for me it doesn't make sense because the lifetime of WithMutRef is irrelevant here. All we need is to make sure that those two structs references a data with the same lifetime. What am I missing?

The version you posted that compiles is overly restrictive. Here is a less restrictive version:

struct WithMutRef<'a, T> {
    r: &'a mut T,
}

struct WithRef<'a, T> {
    r: &'a T,
}

impl<'a, 'b, T> From<&'a WithMutRef<'b, T>> for WithRef<'a, T> {
    fn from(src: &'a WithMutRef<'b, T>) -> WithRef<'a, T> {
        WithRef{r: src.r}
    }
}

impl<'a, 'b, T> From<&'a mut WithMutRef<'b, T>> for WithRef<'a, T> {
    fn from(src: &'a mut WithMutRef<'b, T>) -> WithRef<'a, T> {
        WithRef{r: src.r}
    }
}

The problem is that a mutable reference must have exclusive access. Any time you create a "sub-reference" from the mutable reference, the mutable reference must be disabled for the duration of that sub-reference, since using the mutable reference while the sub-reference exists contradicts the exclusivity of the mutable reference.

The way that this manifests in lifetimes is that &'short &'long mut T flattens to &'short T rather than &'long T, preventing the flattened reference from being used for longer than the &'short &'long mut T could be used. This is different from what happens when the inner reference is immutable, as there's no such exclusivity requirement for immutable references.

1 Like

I really don't understand how lifetimes are combined in this fashion. I used to think that lifetime is an immanent property of data being referenced, basically it's scope.

A lifetime is the duration in which a variable is borrowed, not the duration in which the variable exists.

(Technically speaking, the lifetime is also allowed to be shorter than the duration in which the variable is borrowed. For example, given an &'long T, you can convert it to an &'short T even though this doesn't change how long the borrow lasts for.)

2 Likes

Another way to see lifetimes is that if you have a &'a T, then that's a pointer along with a promise that its safe to use it anywhere inside the region 'a.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.