Lifetime issue with associated types

Hello, I want to have State that I know will never borrow from View, but I can't convince the borrow checker. Adding 'static bound, which should make it clear that State cannot borrow, doesn't help.

I remember similar issue but with opaque types: `impl Trait + 'static` is not `'static` if returned from a generic function · Issue #76882 · rust-lang/rust · GitHub.

Edit: I replaced &mut with &, error still holds. I hoped that maybe using interior mutability as a workaround.

Code (playground):

trait View {
    type State: 'static;

    // initialize the state
    fn build_state(&self) -> Self::State;
    // `view` uses `self` and `state` to do computations. Return type omitted.
    fn render_tree(&self, state: &mut Self::State);
}

impl View for () {
    type State = ();

    fn build_state(&self) -> Self::State {
        ()
    }
    fn render_tree(&self, _state: &mut Self::State) {}
}

fn view_fn<'a>(_: &'a ()) -> impl View + use<'a> {
    ()
}

fn main() {
    let view = view_fn(&());
    let mut state = view.build_state();
    for _ in 0..10 {
        let unit = ();
        let view = view_fn(&unit);
        view.render_tree(&mut state);
    }
}

Error:

 1  error[E0597]: `unit` does not live long enough
   --> src/main.rs:26:28
    |
 25 |         let unit = ();
    |             ---- binding `unit` declared here
 26 |         let view = view_fn(&unit);
    |                            ^^^^^ borrowed value does not live long enough
 27 |         view.render_tree(&mut state);
 28 |     }
    |     - `unit` dropped here while still borrowed
 29 | }
    | - borrow might be used here, when `view` is dropped and runs the destructor for type `impl View`
    |
    = note: values in a scope are dropped in the opposite order they are defined

 For more information about this error, try `rustc --explain E0597`.
 error: could not compile `play` (bin "play") due to 1 previous error
1 Like

the error is not about VIew::State borrowing Self, it has something to do with the opaque return type of view_fn().

for now, there's no way for us to express the condition to the type checker that the hidden type does not have drop glue.

to prove a point, if you change the signature of view_fn to add Copy bound to the opaque type, the code should compile:

fn view_fn<'a>(_: &'a ()) -> impl View + Copy + use<'a>;

but a type doesn't have drop glue is NOT necessarily Copy. we need something like !Drop as bound (if Drop is default like Sized. alternatively, we can make Drop an explicit bound and !Drop the default, either way should be usable), but we are not there (yet?).

or, (although it's probably not possible for your use case, since you use an explicit use<> capture list, but just in case,) if you can make the view_fn() NOT capture the lifetime, then it doesn't matter whether the hidden type has drop glue.

fn view_fn(_: ()) -> impl View + use<>;

Thanks for a try, but this is a requirement to support drop glue for View and have that 'a captured.

sorry, I misread the compile error message.

the real reason of the error message is the type checker needs to unify the type of variable view in the outer scope and the view in the inner loop.

let's give the opaque type returned by view_fn() a name, say UnitVIew<'a>, the problem is, <UnitView<'a> as View>::State (the type of the state variable in the outer scope) and <UnitView<'b> as View>::State (the expected type of the argument of view.render_tree(state) in the inner loop) is NOT the same type, unless 'a and 'b is the exact same lifetime (subtyping is not sufficient).

consider make State a generic parameter instead of associated type.

2 Likes

That's not possible, because each View requires some data that it defines itself.

And they compose. Generics generics are anyway only achievable with associated types, so it will end up with associated types even if there somehow were generics. But this is beyond the issue.

then you must manually unify the State associated type for all lifetimes:

fn view_fn<'a>(_: &'a ()) -> impl View<State = ()> + use<'a>;
1 Like

I can't type the type manually, they are extremely huge and opaque in practice. Please, I would like to withhold from workarounds :sweat_smile:

!Drop wouldn't solve the problem, since Drop bounds are not about drop glue and it would be a breaking change to make it be about drop glue. It would have to be something new.


Is it also a requirement to keep the original view around? This compiles.

     let view = view_fn(&());
     let mut state = view.build_state();
+    drop(view);
     for _ in 0..10 {

We can't read your mind to know which workarounds are acceptable or not......

2 Likes

Another potential workaround:

fn view_fn<'a>(_: &'a ()) -> Box<dyn View<State = impl Sized + use<>> + 'a> {
    Box::new(())
}

The Box is because you can't nest impl Trait, sadly. TAIT would fix that part.

type ViewFnState = impl Sized;

#[define_opaque(ViewFnState)]
fn view_fn<'a>(_: &'a ()) -> impl View<State = ViewFnState> + use<'a> {
    ()
}

Perhaps an expansion of this logic in the compiler would allow the OP to compile. You can read that PR and the PR it replaced for more context on why lifetimes can't just be soundly discarded despite the 'static bound.

3 Likes

My bad, agree.


Turns out, if State: 'static AND drop the view, it compiles. I tried both but separately, for example with that view: I skipped the view.build_state() entirely and just used unsafe { zeroed() } (code can't compile anyway), but error remained :sweat_smile: . Now while trying to improve the example (to show that removing view not helps in practic) I discovered that it actually helps if State: 'static. I can afford to combine those things. Thank you all for help!

2 Likes

As a workable solution as found, this probably isn't of interest, but I found a way to indirectly express this via traits and bounds on stable without modifying the original trait. So I'll throw it out there anyway:

Probably only works when Arg: 'static due to the usual HRTB woes.

1 Like

Wow, that could be considered as a form of art

Thanks, but it just occurred to me that it is probably useless to you in practice because of the annoying closure inference problems...

fn view_maker_alt() -> impl UnifiedMaker<str> {
    fn le_sigh<F: Fn(&str) -> &str>(f: F) -> F { f }
    // `le_sigh` was required to guide inference as the `Fn` supertrait
    // bound on `UnifiedMarker` does not override inference like a direct
    // bound does (and the compiler will not figure out that you wanted to
    // return something with the same lifetime as a closure argument without
    // that override).
    le_sigh(|s| s)
}

...since a requirement of yours was not having to name the return types.

...
    for _ in 0..10 {
        let view = view_fn(&());
        view.render_tree(&mut state);
    }
...

also works due to rvalue static promotion.