Issues with lifetimes and Higher-Rank Trait Bounds

I am playing a bit with the magic-handler pattern and have stumbled into issues regarding lifetimes and Higher-Rank Trait Bounds (HRTB's). Here is the Playground link.

Lets start with a trait for generic function parameters and retrieving them from a store.

pub trait Param<'store, S> {
    fn fetch(store: &'store S) -> Self;
}

// For simplicity the store is just that.
type Store = usize;

We implement the Param trait for a reference to a usize. With the lifetime argument to the Param trait, we can express that Self is not outliving the store.

impl<'store> Param<'store, Store> for &'store usize {
    fn fetch(store: &'store Store) -> Self {
        store
    }
}

This is the parameter function trait. It should not care in detail about the 'store lifetime.

pub trait FnStore<S, P> {
    fn call_with_store(&self, store: &S);
}

For now FnStore is implemented for all functions with exactly one arbitrary parameter for which the Param trait is implemented. Here we use HRTB's to provide a 'store lifetime (or many?).

impl<F, S, P1> FnStore<S, (P1,)> for F
where
    F: Fn(P1),
    // Provide the not yet available 'store lifetime
    // without extending the FnStore trait.
    P1: for<'store> Param<'store, S>,
{
    fn call_with_store(&self, store: &S) {
        // The fetched parameter only needs to be alive
        // within this block.
        let p1 = P1::fetch(store);
        // Here the implicit 'store lifetime is known but
        // it can not be utilized explicitly beforehand,
        // since the FnStore trait has no lifetime
        // argument for it.
        (self)(p1)
    }
}

Now we define a test function which takes exactly one usize by reference for which the Param trait has been implemented.

fn test_fn(_: &usize) {}

But it does not have a call_with_store method?!

fn main() {
    let store = 42 as Store;
    test_fn.call_with_store(&store)
}

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0599]: no method named `call_with_store` found for fn item `for<'a> fn(&'a usize) {test_fn}` in the current scope
  --> src/main.rs:46:13
   |
46 |     test_fn.call_with_store(&store)
   |             ^^^^^^^^^^^^^^^ method not found in `fn(&usize) {test_fn}`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
note: `FnStore` defines an item `call_with_store`, perhaps you need to implement it
  --> src/main.rs:17:1
   |
17 | pub trait FnStore<S, P> {
   | ^^^^^^^^^^^^^^^^^^^^^^^

Why is the call_with_store method not available? And how can I make it available? Should I extend the FnStore trait with a 'store lifetime? Whats the best practice to name or refer to such a lifetime? Or is there another approach?

Also trying to call call_with_store like this:

fn main() {
    let store = 42 as Store;
    FnStore::<Store, (&usize,)>::call_with_store(&test_fn, &store)
}

gives another compilation error:

Compiling playground v0.0.1 (/playground)
error: implementation of `Param` is not general enough
  --> src/main.rs:52:5
   |
52 |     FnStore::<Store, (&usize,)>::call_with_store(&test_fn, &store);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Param` is not general enough
   |
   = note: `Param<'0, usize>` would have to be implemented for the type `&usize`, for any lifetime `'0`...
   = note: ...but `Param<'1, usize>` is actually implemented for the type `&'1 usize`, for some specific lifetime `'1`

What does that mean? How can I fix it?

Additional note:
It all started because I wanted to express that a reference type implementing the Param trait should not outlive the store. Or even more restrictive, that the lifetime of a fetched parameter is bound to the scope of its surrounding block, aka. the call_with_store method. I achieved that by exposing the 'store lifetime with a second trait parameter of the Param trait. But this got me into trouble naming/providing it in the FnStore implementations ... which led me to make this post.
So, maybe there is another way to solve the origin of my issue?

I haven’t tried to fully understand the context yet, but I can easily answer the main question for the linked playground:

Your trait bound requires

    F: Fn(P1),
    P1: for<'store> Param<'store, S>,

which means that F must support one parameter type P1, and that single parameter type P1 then must implement Param<'store, S> for all lifetimes 'store.

You then try to fulfill this for test_fn, a function with signature fn(&usize), which is equivalent to the more explicit for<'a> fn(&'a usize). This function supports lots of parameter types, it supports parameter &'foo usize for any lifetime 'foo. Let’s call any such type P1. Now our single type P1, which is some type of the form &'foo usize for a single (arbitrary) lifetime 'foo would be required to implement Param<'store, S> for all lifetime 'store in order to fulfill the FnStore implementation for test_fn.

However your actual trait implementation

impl<'store> Param<'store, Store> for &'store usize

is written such that P1 == &'foo usize will only be implemented for a single lifetime 'store == 'foo. This is true regardless of how 'foo is chosen, so FnStore<Store, (&usize,)> cannot possibly be implemented for (the type of) the function item test_fn.

3 Likes

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.