Skip to content

Mutable reference doesn't coerce to a smaller lifetime #79886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mbartlett21 opened this issue Dec 10, 2020 · 3 comments
Closed

Mutable reference doesn't coerce to a smaller lifetime #79886

mbartlett21 opened this issue Dec 10, 2020 · 3 comments
Labels
C-bug Category: This is a bug.

Comments

@mbartlett21
Copy link
Contributor

I tried the code below.
The one that uses immutable references works and coerces to a smaller lifetime, but the one that uses mutable references doesn't.

// This one is fine
struct Scoped<'a>(Option<&'a Scoped<'a>>);

impl<'a> Scoped<'a> {
    fn scoped<'b>(&'b self) -> Scoped<'b>
    where
        'a: 'b,
    {
        Scoped::<'b>(Some(self))
    }
}

// This one doesn't work
struct ScopedMut<'a>(Option<&'a mut ScopedMut<'a>>);

impl<'a> ScopedMut<'a> {
    fn scoped<'b>(&'b mut self) -> ScopedMut<'b>
    where
        'a: 'b,
    {
        ScopedMut::<'b>(Some(self))
    }
}

(playground)

Output:

rustc scoping.rs --edition 2018
error[E0308]: mismatched types
  --> scoping.rs:21:30
   |
21 |         ScopedMut::<'b>(Some(self))
   |                              ^^^^ lifetime mismatch
   |
   = note: expected mutable reference `&mut ScopedMut<'b>`
              found mutable reference `&mut ScopedMut<'a>`
note: the lifetime `'b` as defined on the method body at 17:15...
  --> scoping.rs:17:15
   |
17 |     fn scoped<'b>(&'b mut self) -> ScopedMut<'b>
   |               ^^
note: ...does not necessarily outlive the lifetime `'a` as defined on the impl at 16:6
  --> scoping.rs:16:6
   |
16 | impl<'a> ScopedMut<'a> {
   |      ^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

Meta

rustc --version --verbose:

rustc 1.50.0-nightly (f0f68778f 2020-12-09)
binary: rustc
commit-hash: f0f68778f798d6d34649745b41770829b17ba5b8
commit-date: 2020-12-09
host: x86_64-pc-windows-msvc
release: 1.50.0-nightly

(reposted from #79879 (comment))

@SkiFire13
Copy link
Contributor

I think this is working as intended, since 'a appears inside an &mut reference (that &mut ScopedMut<'a> inside the Option) then ScopedMut<'a> is invariant with respect to 'a. The reason is that you could replace that ScopedMut<'a> with a ScopedMut<'b> were 'a: 'b but then later read the original ScopedMut<'a> as if it has lifetime 'a (which is obviously false!)

@jonas-schievink
Copy link
Contributor

Yeah, working as intended

@mbartlett21
Copy link
Contributor Author

mbartlett21 commented Dec 10, 2020

Ok. It seems to be when mutable references or UnsafeCell are involved. This fails to compile:

use core::cell::UnsafeCell;

struct Scoped<'a>(UnsafeCell<Option<&'a Scoped<'a>>>);

fn scoped<'a: 'b, 'b>(s: &'b Scoped<'a>) -> Scoped<'b> {
    Scoped(UnsafeCell::new(Some(s)))
}

Here is an example of something that is very bad that could be done if it allowed it:

use core::mem::transmute;

struct Scoped<'a>(Option<&'a mut Scoped<'a>>);

unsafe fn scoped<'a: 'b, 'b>(scoped: &'b mut Scoped<'a>) -> Scoped<'b> {
    // This is only safe if we promise to not change the
    // mutable reference that `scoped` holds.
    Scoped(unsafe { transmute(scoped) })
}

// This changes `scoped` to hold a reference to a dropped struct
fn bad(scoped: &mut Scoped<'_>) {
    let mut borrowed_scope = Scoped(None);
    let mut scoped_s = unsafe { scoped(scoped) }; // This shortens the used lifetime.
    scoped_s.0.as_mut().unwrap().0 = Some(&mut borrowed_scope);
}

(playground link)

Should the error possibly be improved to explain that mutable references cannot be coerced to a smaller lifetime and give an example?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug.
Projects
None yet
Development

No branches or pull requests

3 participants