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?