Generic Associated Types and lifetime

In the following code (full code available here), if I'm using the method run, the compiler tells me that &mut store should be borrowed for 'static due to current limitations in the borrow checker (error output below). What is this limitation?

I can fix this by introducing a lifetime in run (see run_with_unsafe) but it makes it impossible to use the lending iterator due to how the lifetimes become bound. To fix this new problem, I'm using transmute, but is this safe to do so?

fn main() {
    let mut store = MyStore {
        product_names: vec![
            String::from("Grapes"),
            String::from("Table"),
            String::from("Pencil"),
        ],
    };

    let worker = Worker::new(&mut store);

    worker.run(|product| {
        product.set_name(String::from("Banana"));
    });

    for name in store.product_names {
        println!("{name}");
    }
}

impl<S> Worker<S>
where
    S: Store,
{
    pub fn run(self, mut handler: impl FnMut(&mut S::Product<'_>)) {
        let Self { mut store } = self;

        let mut products = store.products();

        while let Some(mut product) = products.next() {
            handler(&mut product);
        }
    }

    pub fn run_with_unsafe<'a>(self, mut handler: impl FnMut(&mut S::Product<'a>))
    where
        S: 'a,
    {
        let Self { mut store } = self;

        let mut products = store.products();

        while let Some(mut product) = products.next() {
            // Is it safe ?
            let product_borrow: &mut <S as Store>::Product<'a> =
                unsafe { core::mem::transmute(&mut product) };

            handler(product_borrow);
        }
    }
}
error[E0597]: `store` does not live long enough
  --> src/main.rs:16:30
   |
8  |       let mut store = MyStore {
   |           --------- binding `store` declared here
...
16 |       let worker = Worker::new(&mut store);
   |                                ^^^^^^^^^^ borrowed value does not live long enough
...
22 | /     worker.run(|product| {
23 | |         product.set_name(String::from("Banana"));
24 | |     });
   | |______- argument requires that `store` is borrowed for `'static`
...
29 |   }
   |   - `store` dropped here while still borrowed
   |
note: due to current limitations in the borrow checker, this implies a `'static` lifetime
  --> src\worker.rs:17:40
   |
17 |     pub fn run(self, mut handler: impl FnMut(&mut S::Product<'_>)) {
   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^

Please post the full error from running cargo check or cargo build in the terminal.

1 Like

I think I have filled in the gaps enough to reproduce the error: Rust Playground

The compiler finds that the only suitable lifetime for '_ in FnMut(&mut S::Product<'_>) is 'static. To fix this, we can constrain the lifetime explicitly, chosen by the caller:

    pub fn run<'a>(&'a mut self, mut handler: impl FnMut(&mut S::Product<'a>)) {
        // ...
    }

This changes the calling semantics but allows the compiler to interpret an appropriate shorter lifetime.

There are some other unusual things about this code. FnMut isn't necessary because the closure does not take an exclusive reference to its environment. The reference is passed to the Worker constructor! And second, taking a temporary exclusive reference to S::Product<'a> is also not needed, because there is already an inner reference (whose lifetime annotation is named 'a).

This can be simplified further to:

    pub fn run<'a>(&'a mut self, handler: impl Fn(S::Product<'a>)) {
        // ...
    }

I should also raise the point that the temporary Store::Product view type itself is unusual. If you make the implementer own this type, then that hidden reference goes away and you can go back to making Worker::run() move self. This also brings back the temporary exclusive reference to the (no longer temporary) Store::Product:

    pub fn run(self, handler: impl Fn(&mut S::Product)) {
        // ...
    }

There is a link in the OP to the fullcode.

This is what I understand too and also the solution I use. But my point here is to understand why is 'static required while it seems it shouldn't.

Well, this is a minimal version of one of my project. The technical choices depend of the rest of the project.
The point of my post is to understand why the compiler have those limitations and how to workaround them.

The naive way to look at this is that '_ does not constrain the lifetime, so it must be satisfiable by all lifetimes including 'static.

My understanding of the elided lifetime '_ is that the compiler automatically add a lifetime to the function, so that

fn foo(x: Foo<'_>) {}

is a sugar syntax for

fn foo<'a>(x: Foo<'a>) {}

But it don't explains why is 'static is required.
Also the compiler error claims that if it chooses 'static it's because of a limitation of the borrow checker. Is there any ressource somewhere which documents that?

The difference is that you're using lifetime elision with the FnMut trait. There are two things to note:

  1. The error message clearly points out there is a "current limitation in the borrow checker". (This might be GATE: wrong lifetime analysis · Issue #113721 · rust-lang/rust It isn't. See below.)

  2. Closures used as trait bounds have different lifetime elision rules.

    fn foo(x: impl Fn(Foo<'_>)) {}
    

    Desugars as:

    fn foo(x: impl for<'a> Fn(Foo<'a>) + 'static) {}
    

I wish I had a better reference. The git blame for the "current limitations" error message might have some historical context.

edit: This PR added the error message: Add 'static lifetime suggestion when GAT implied 'static requirement from HRTB by yanchen4791 · Pull Request #106747 · rust-lang/rust

The PR comment links to Generic associated types to be stable in Rust 1.65 | Rust Blog to explain the implied 'static requirement.

3 Likes

You're running into the issues described in this article. Applying this workaround:

    pub trait StoreGat<'this, Lt = &'this Self> {
        type Product: Product;
    }

    pub trait Store: for<'a> StoreGat<'a> {
        fn products<'product, 'iterator: 'product, 'this: 'iterator>(
            &'this mut self,
        ) -> impl ProductIterator<
            Item<'product> = <Self as StoreGat<'product>>::Product
        > + 'iterator;
    }

Allows run to be used with no unsafe.[1]


I note that you've already worked around some HRTB+GAT issues using single lifetimes baked into your API:

You have a lending iterator crate, where the items can borrow from &mut self when you call next.

    pub trait ProductIterator {
        type Item<'this>: Product
        where
            Self: 'this;

        fn next(&mut self) -> Option<Self::Item<'_>>;
    }

(Although you only define it when a normal iterator would do -- when the items do not borrow from &mut self when you call next.)

The Store trait has this method:

    fn products<'product, 'iterator: 'product, 'this: 'iterator>(
        &'this mut self,
    ) -> impl store::ProductIterator<Item<'product> = Self::Product<'product>> + 'iterator

What you probably desired was something like

-> impl for<'a> store::ProductIterator<Item<'a> = Self::Product<'a>>

I didn't try to tackle this one.


  1. I didn't try to find an exploit, but circumventing the borrow checker with unsafe almost always has a soundness hole. ↩︎

1 Like

Part 1: Nougats

Speaking of which, and for the sake of having some form of naming for these things, this is what the

crate does, and from there I personally like to call these pseudo-GATs "Nougats", which by having this more "raw / true form", happen to avoid many of the limitations which GATs have.


This has been the more challenging part of the snippet, actually, since using the proper bound yields an unsatisfied lifetime bound.

But first of all, let's clear the lifetime names quite a bit, shall we? (I do appreciate the fact some naming was attempted, rather than just having 'a, 'b, 'c as the unfortunate bad habit taught by the Rust book.

This helps show a confusion about the lifetimes involved.

The lifetime involved in a lending iterator is perhaps ideally named 'next, rather than 'this.

Now, here there is a fn products(&'_ mut self) -> impl '_ + LendingIter…, so here we very much not have the "same 'this (now named 'next)" we've been using elsewhere. Rather, this is more of your 'iterator notion. I personally like to name lifetimes here related to the method name whence the borrow starts (e.g., 'next before). Here, thus, 'products:

fn products<'products>(&'products mut self)
  -> impl 'products + for<'next> ProductIterator<
        // Using a GAT.
        Item<'next> = ProductHandler<'next>,
    >
{
    self.product_names
        .iter_mut()
        .map(|name: &'products mut String| {
            ProductHandler::<'_> { name }
                          // ^ which lifetime goes here?
        })
}
  • (I've added a few extra lifetime annotations.)

This yields "weird" lifetime errors w.r.t. the fn body.

The reason for this is that the above iterator is not a strictly lending iterator. Rather, it is a good old iterator, for which you have a blanket impl to become a trivial lending iterator.

  • FWIW, we could make it strictly lending by reborrowing, but that needs a special design which would let us "know" of the 'next lifetime (notice how in the fn body here it is nowhere to be seen!); we'll probably see that further down below.

In other words, we have:

impl for<'next> ProductIterator<
    Item<'next> = ProductHandler<'products>
                              // ^
                              // rather than `'next`
>
  • and with also a rather important, and yet subtle nuance, here: the for<'next> quantification needs to be upper-bounded by 'products, in order for it to be well-formed: in pseudo-code, it would be for<'next where 'products : 'next>.

    And here, the GAT usage on ProductIterator fails to express this, and instead, makes the for<'next> range over all lifetimes, all the way up to : 'static, which thus is only correct iff 'products : 'static.

Hence the lifetime error on that fn.

The solution, as usual, is to forgo GATs, and use Nougats (or the other alternative I'll mention down below).

So we Nougatify ProductIterator, and now the bound can be expressed, but for the uglyness of the syntax needed to add a higher-ranked/for<>-quantified assoc type equality bound for a Nougatified trait:

fn products<'products>(
    &'products mut self,
) -> impl
        'products +
        ProductIterator +
        for<'next> store::ProductIteratorNougat<
            'next,
            Item = <Self as StoreNougat<'products>>::Product,
        > +
{
    self.product_names
        .iter_mut()
        .map(|name: &'products mut String| {
            ProductHandler::<'products> { name }
        })


Part 2: FATs

Click here to skip this section

Now, in order to make this more lightweight, there is another rather useful and quite powerful pattern, which avoids both:

  • the : 'static bugs/limitations from GATs,
  • the cumbersome aspect of non-::nougat-using Nougats.
  • (and which is dyn-able! :fire:)

The key idea being to switch from:

type GAT<'lt>;

to:

type FAT : <'lt>; // "can be fed `'lt`"

which would be possible to express if we were able to have "(lifetime-)generic types" be, in and of themselves, types already. Also called "type ctors as types", and all this belongs to the realm of higher-kinded types.

  • to be strictly pedantic, such a generic type would not be higher-kinded, just "arrow-kinded" in Haskell parlance. An API involving arrow-kinded types is the one being called higher-kinded.

    Here, for instance, a trait with a FAT could thus be legitimately deemed to be a higher-kinded trait.

So, how do we call these "arrow-kinded types" / type-ctors-as-types? Well, the closer we have to these things, in Rust, is when using for<'lt> quantifications.

Interlude: For[Lt] types

click to see

For instance, consider the following two types:

type A = fn(&()) -> &str;
    // = for<'lt> fn(&'lt ()) -> &'lt str
type B = fn(&()) -> &'static str;
    // = for<'lt> fn(&'lt ()) -> &'static str

Both A and B are, themselves, types, and we can "feed them" some 'lt (by imagining what would happen if we fed a &'lt () arg to the given fn callback, and looking at the return type), and from there, we can either get &'lt str or &'static str, respectively.

In other words, in pseudo-code, we have:

type A ~ for<'lt> &'lt str;
type B ~ for<'lt> &'static str;

Well, it turns out we can actually define such a design and abstraction, with a helper macro for these things, which we can call For!:

use ::higher_kinded_types::extra_arities::For;

type A = For!(<'lt> = &'lt str);
type B = For!(<'lt> = &'static str);

Here, we are "hard-coding" a type-arity of 'lifetime => Ty, so I also call them ForLt types, and with an eponymous macro which even allows lifetime-elision syntax:


use ::higher_kinded_types::ForLt;

type A = ForLt!(&str) /* = ForLt!(<'lt> = &'lt str) */;
type B = ForLt!(&'static str) /* = ForLt!(<'lt> = &'static str) */;

These types implement the eponymous ForLt trait, which lets one "feed" them any arbitrary 'lt by fetching its own Of<'lt> (Generic) associated type.

Hence the terminology of For[Lt] types, which, when used as associated types of a trait, yield For[Lt] Associated Types, or FATs for short.

Example: migrating from GATs to FATs:

  1. Definition:

    type Item<'next> /* where Self : 'next */;
    

    is to become:

    type Item : ForLt;
    
    • If type Item<'next> had : Bounds, defining a helper refining "subtrait" of ForLt will be handy:

          type Item<'next> : Product;
      }
      

      is to become:

          type Item : ForLtImplsProduct;
      }
      
      /// with the following "trait alias":
      pub
      trait ForLtImplsProduct
      where
          Self : for<'any> ForLt<Of<'any> : Product>,
      {}
      impl<T> ForLtImplsProduct for T
      where
          Self : for<'any> ForLt<Of<'any> : Product>,
      {}
      
  2. Mentions / usage of the associated type

    Self::Item<'next>
    

    is to become:

    <Self::Item as ForLt>::Of<'next>
    

    If this is deemed too cumbersome, a handy alias can be defined:

    type Feed<'lt, T> = <T as ForLt>::Of<'lt>;
    

    so as to, instead, write:

    Feed<'next, Self::Item>
    

    Or, focusing on the newly defined trait:

    type Item<'next, T> = <<T as MyTrait>::Item as ForLt>::Of<'next>;
    
  3. Implementations

    type Item<'next> = &'next mut T;
    

    is to become:

    type Item = ForLt!(<'next> = &'next mut T);
    // or, using the elision shorthand:
    type Item = ForLt!(&mut T);
    

Part 3: Using FATs for ProductIterator

pub
trait ForLtImplsProduct
:
    for<'any> ForLt<Of<'any> : Product> +
{}

impl<T> ForLtImplsProduct for T
where
    Self : for<'any> ForLt<Of<'any> : Product>,
{}

pub trait ProductIterator {
    type Item : ForLtImplsProduct;

    fn next(&mut self) -> Option<Feed<'_, Self::Item>>;
}

If we were to keep Store using Nougats, we'd end up with the following definition:

pub
trait StoreNougat<'p, _Bounds = &'p Self> {
    type Product : Product;
}

pub
type StoreProduct<'p, T> = <T as StoreNougat<'p>>::Product;

pub trait Store : for<'products> StoreNougat<'products> {
    fn products<'products>(
        &'products mut self,
    ) -> impl 'products + ProductIterator<
        Item = ForLt!(<'next> = StoreProduct<'products, Self>),
    >;
}

and our concrete impl:

impl<'products> StoreNougat<'products> for MyStore {
    type Product = ProductHandler<'products>;
}

impl Store for MyStore {
    fn products<'products>(
        &'products mut self,
    ) -> impl 'products + ProductIterator<
        Item = ForLt!(store::StoreProduct<'products, Self>),
    >
    {
        self.product_names
            .iter_mut()
            .map(|name: &'products mut String| {
                ProductHandler::<'products> { name }
            })
    }
}
  • Playground

    There is an interesting diagnostic quirk here when mixing up Nougats and FATs, wherein Rust is convinced our impl is more "refined" than what the trait required… :person_shrugging:

Part 4: making MyStore::products strictly lending

so that its Item be a For!(<'next> = ProductHandler<'next>) rather than a For!(<'next> = ProductHandler<'products>).

Here it is "unnecessary", and does mean restricting what the caller might be able to do with it (e.g., forced to use lending iteration, rather than normal iteration).

That being said, it otherwise feels quite weird, and unnecessary, to be committing a whole LendingIterator pattern, only for the actual definition to be restricted to classic Iterator. So I think the current signature is an "oversight", and that the author did intend to get a fully lending signature.

The problem then, is that even if ProductHandler<'_> is covariant (over <'_>, meaning its '_ can "shrink"), once you go into the reälm of traits, that no longer applies, and the two aforementioned For!() types for Item are actually distinct, and an implementation yielding the latter (like it currently does), will not satisfy the former For!(<'next> = ProductHandler<'next>) requirement.

So we need to define a properly lending implementation.

The problem is, then, that the code given here provides no such tools to do that, other than by manually implementing everything from scratch, which would be tiresome, and rather uninteresting.

So let's instead, do that, but for a more general-purpose helper, and then let's use such a helper to implement our .products().

What is the more general-purpose iterator constructor possible?

Something akin to iter::from_fn(). But since we don't have LendingFnMut yet, the way to polyfill that is by extracting the lent state outside of the closure's implicit captures, and "thread it through", explicitly:

pub
struct FromFnIterator<State, F, Ret : ForLtImplsProduct>
where
    F : FnMut(&'_ mut State) -> Option<Ret::Of<'_>>,
{
    pub state: State,
    pub next: F,
    pub _phantom: ::core::marker::PhantomData<Ret>,
}

impl<State, F, Ret : ForLtImplsProduct>
    ProductIterator
for
    FromFnIterator<State, F, Ret>
where
    F : FnMut(&'_ mut State) -> Option<Ret::Of<'_>>,
{
    type Item = Ret;
    
    fn next(&mut self) -> Option<Feed<'_, Ret>> {
        (self.next)(&mut self.state)
    }
}

With it, we can finally write:

impl<'next> StoreNougat<'next> for MyStore {
    type Product = ProductHandler<'next>;
}

impl Store for MyStore {
    fn products<'products>(
        &'products mut self,
    ) -> impl 'products + ProductIterator<
        Item = ForLt!(<'next> = store::StoreProduct<'next, Self>),
    >
    {
        store::FromFnIterator::<_, _, ForLt!(store::StoreProduct<'_, Self>)> {
            state: self.product_names.iter_mut(),
            next: |iter /* : &'next mut state */| {
                let name: &'products mut String = iter.next()?;
                Some(ProductHandler {
                    // reborrowed as &'next mut String
                    name
                })
            },
            _phantom: <_>::default(),
        }
    }
}
  • Here we find a second handy usage of ForLt! types: to get proper, fully arbitrary, higher-order signature inference/nudging for the given closure.

  • Playground

Part 5: Replacing StoreNougat with its own FAT

This is now for the sake of simplicity / consistency in the logic being used, and which has the happy side-effect of getting rid of that weird lint:

impl Store for MyStore {
    type Product = ForLt!(ProductHandler<'_>);
    fn products<'products>(
        &'products mut self,
    ) -> impl 'products + ProductIterator<
        Item = Self::Product,
    >
    {
        store::FromFnIterator::<_, _, Self::Product> {
            state: self.product_names.iter_mut(),
            next: |iter| {
                let name: &'products mut String = iter.next()?;
                Some(ProductHandler {
                    // reborrows as &'_ mut String
                    name
                })
            },
            _phantom: <_>::default(),
        }
    }
}

The main thing I find quite elegant here is the "eta reduction" of the Item equality constraint.

Rather than defining:

Item = ForLt!(<'next> = Self::Product::Of<'next>)

we've been able to write:

Item = Self::Product

Disclaimer

Too much usage of ForLt types could run into, in very edge cases, rustc unsoundess. See Seems to rely on behavior related to known soundness bug · Issue #5 · danielhenrymantilla/lending-iterator.rs · GitHub and the issues mentioned there for more info.

1 Like

I'd like to also comment on this, and using a separate post to give this part higher visibility.

:warning: This operation, in the more general case, is unsound :warning:.

The Some(mut product) = products.next() {-bound product is thus using the lifetime of the &mut borrow of products which happens there and then (e.g., it is a borrow which does not span across iterations).

So, the resulting S::Product<'_>, in and of itself, is not usable across iterations.

Consider, for instance, the following lending iterator, in pseudo-code:

// type Product<'a> = &'a str;
fn next(&mut self) -> Option<&str> {
    self.storage = String::from("hello");
    Some(&self.storage)
}

If we call this twice, when about to lend the second item, the previous self.storage is overwritten (and thus dropped, and the heap str contents, freed). So we very much need the previously lent item not to be accessible by now, since the &str would now be referring/pointing to freed, dangling memory!

But the signature of fn run_with_unsafe<'a>, is letting the caller of this function pick their own flavor of 'a: &mut &'a str in this example. And while any such choice would already be problematic, let's exacerbate that by imagining their picking 'a = 'static.

Then it means that that callback will be receiving a &mut &'static str, which they can reborrow as & &'static str, and from there extract the &'static str out of it all (note: if things weren't Copy/Clone, it is still likely for mem::replace() shenanigans to allow extracting a T out of a &mut T), which the borrow checker will obviously let be used across iterations and whatnot.

2 Likes