Skip to content
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

Lifetime mismatch with Option as_mut and map #91292

Open
Yuri6037 opened this issue Nov 27, 2021 · 9 comments
Open

Lifetime mismatch with Option as_mut and map #91292

Yuri6037 opened this issue Nov 27, 2021 · 9 comments
Labels
A-inference Area: Type inference A-lifetimes Area: lifetime related

Comments

@Yuri6037
Copy link

Yuri6037 commented Nov 27, 2021

Consider the following code:

use std::ops::DerefMut;
use std::ops::Deref;

pub trait SectionData: std::io::Read + std::io::Write + std::io::Seek {}

pub struct SectionMut<'a>
{
    data: &'a mut Option<Box<dyn SectionData>>,
}

impl<'a> SectionMut<'a>
{
    pub fn open(&mut self) -> Option<&mut dyn SectionData>
    {
        //self.data.as_mut().map(|v| v.deref_mut()) //Lifetime mismatch
        match &mut self.data { //Works
            Some(v) => Some(v.deref_mut()),
            None => None
        }
    }

    pub fn open1(&self) -> Option<&dyn SectionData>
    {
        self.data.as_ref().map(|v| v.deref()) //Works
    }
}

I find it strange that a match statement works but not the map function with as_mut. On the IRC a user found a way to fix it by replacing Option<&mut dyn SectionData> with Option<&mut dyn SectionData + 'static>. Which is also weird considering 'static is supposed to be implicit. What's also weird is this works perfectly with as_ref alone.

EDIT: I could track the issue down to dyn (again). When removing the dyn SectionData and replacing it with a concrete struct everything works fine.

@PatchMixolydic
Copy link
Contributor

PatchMixolydic commented Nov 27, 2021

The diagnostic in question:

error[E0308]: mismatched types
  --> src/lib.rs:15:9
   |
15 |         self.data.as_mut().map(|v| v.deref_mut()) //Lifetime mismatch
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected enum `Option<&mut dyn SectionData>`
              found enum `Option<&mut (dyn SectionData + 'static)>`
note: the anonymous lifetime defined here...
  --> src/lib.rs:13:17
   |
13 |     pub fn open(&mut self) -> Option<&mut dyn SectionData>
   |                 ^^^^^^^^^
   = note: ...does not necessarily outlive the static lifetime

It seems that &'a dyn Trait is equivalent to &'a (dyn Trait + 'a). Since the lifetime bounds here are inferred, that means that 'a is the anonymous lifetime on &mut self. With the lifetimes revealed, the signature of open is as follows:

pub fn open(&'_ mut self) -> Option<&'_ mut (dyn SectionData + '_)>

I'm not sure off the top of my head what's causing the lifetime mismatch here. It seems like returning dyn SectionData + 'b where dyn SectionData + 'a is expected should require 'b to live for at least as long as 'a, not the other way around, though I might be missing something.

@scottmcm
Copy link
Member

scottmcm commented Nov 27, 2021

Here's another fix that works: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7f61519b420c4725dcacddd9a80f69ee

Two steps to get there. First, since SectionMut is holding a lifetime-restricted reference, it'd be fine for the Box it's referencing to be non-'static:

pub struct SectionMut<'a> {
    data: &'a mut Option<Box<dyn SectionData + 'a>>,
}

Then in the impl, you don't want to tie the trait object's lifetime to the passed-in lifetime, but to the one on the impl:

impl<'a> SectionMut<'a> {
    pub fn open(&mut self) -> Option<&mut (dyn SectionData + 'a)> {

I'm more and more questioning whether the + 'static defaulting rules are worth it...

EDIT: Opened #91302 for that conversation.

@Yuri6037
Copy link
Author

Here's another fix that works: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7f61519b420c4725dcacddd9a80f69ee

Two steps to get there. First, since SectionMut is holding a lifetime-restricted reference, it'd be fine for the Box it's referencing to be non-'static:

pub struct SectionMut<'a> {
    data: &'a mut Option<Box<dyn SectionData + 'a>>,
}

Then in the impl, you don't want to tie the trait object's lifetime to the passed-in lifetime, but to the one on the impl:

impl<'a> SectionMut<'a> {
    pub fn open(&mut self) -> Option<&mut (dyn SectionData + 'a)> {

I'm more and more questioning whether the + 'static defaulting rules are worth it...

EDIT: Opened #91302 for that conversation.

That won't fix the case where the Box is inside of another structure like so:

pub struct SectionEntry
{
    data: Box<dyn SectionData>,
    /* some other private fields in here */
}
pub struct SectionMut<'a>
{
    entry: &'a mut SectionEntry
}

@mbartlett21
Copy link
Contributor

@Yuri6037

The lifetime can simply be propagated to the inner struct:

pub struct SectionEntry<'a> {
    data: Box<dyn SectionData + 'a>,
    // all the private fields you want
}

pub struct SectionMut<'a> {
    entry: &'a mut SectionEntry<'a>,
}

@Yuri6037
Copy link
Author

Yuri6037 commented Nov 30, 2021

@Yuri6037

The lifetime can simply be propagated to the inner struct:

pub struct SectionEntry<'a> {
    data: Box<dyn SectionData + 'a>,
    // all the private fields you want
}

pub struct SectionMut<'a> {
    entry: &'a mut SectionEntry<'a>,
}

Why would I want to restrict my dyn trait to a specific lifetime? It shouldn't be.

@mbartlett21
Copy link
Contributor

It isn't restricting the data behind the Box. What it is doing is lessening the requirements from 'static to 'a. Without an annotated lifetime in Box, Rust assumes you meant 'static, which unnecessarily restricts the allowed types.

With it, you can now use borrowed data in your dyn SectionData + 'a.

E.g:

struct SectionDataWithReference<'a>(&'a u8);
impl SectionData for SectionDataWithReference<'_> { ... }

let num = 5;
let mut entry = SectionEntry {
    data: Box::new(SectionDataWithReference(&num)),
};
let sm = SectionMut {
    entry: &mut entry,
};

@Yuri6037
Copy link
Author

Yuri6037 commented Nov 30, 2021

It isn't restricting the data behind the Box. What it is doing is lessening the requirements from 'static to 'a. Without an annotated lifetime in Box, Rust assumes you meant 'static, which unnecessarily restricts the allowed types.

With it, you can now use borrowed data in your dyn SectionData + 'a.

E.g:

struct SectionDataWithReference<'a>(&'a u8);
impl SectionData for SectionDataWithReference<'_> { ... }

let num = 5;
let mut entry = SectionEntry {
    data: Box::new(SectionDataWithReference(&num)),
};
let sm = SectionMut {
    entry: &mut entry,
};

I think you misunderstood my point with SectionEntry. SectionEntry cannot have a lifetime as it's what's stored in a container and passing the lifetime to the container itself is just increasing complexity of the API and has no benefit because the underlying trait in all cases will always be created in a Box not a lifetime based box.

EDIT: also adding explicit lifetimes in generics adds a restriction which is that all other parent containers must also keep track of this lifetime.

@mbartlett21
Copy link
Contributor

With a limited lifetime, everything* above it has to have a lifetime or generic. E.g: If your container is a Vec, it can be Vec<SectionEntry<'a>>. The reason lifetimes cannot be 'forgotten' is that it could potentially be unsound.

* Allocators are different the user keeps track of types, not the allocator.

@Yuri6037
Copy link
Author

Yuri6037 commented Dec 1, 2021

With a limited lifetime, everything* above it has to have a lifetime or generic. E.g: If your container is a Vec, it can be Vec<SectionEntry<'a>>. The reason lifetimes cannot be 'forgotten' is that it could potentially be unsound.

  • Allocators are different the user keeps track of types, not the allocator.

Yeah I know and that's the problem, restricting dyn traits to have always generic lifetimes in my case renders them completely useless.

The original problem here is that if the return type of the function is explicitly dyn SectionData + 'static option map works but if not it fails whereas in the immutable case it works. It's disturbing to see this inconsistency.

@jyn514 jyn514 added A-lifetimes Area: lifetime related A-inference Area: Type inference labels Apr 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-inference Area: Type inference A-lifetimes Area: lifetime related
Projects
None yet
Development

No branches or pull requests

5 participants