error[E0308]: mismatched types
--> src/main.rs:12:30
|
12 | helper(|func| iter_units(func), |_| ());
| ^^^^ one type is more general than the other
|
= note: expected trait object `dyn for<'a> FnMut(&'a ())`
found trait object `dyn FnMut(&())`
Is it a known issue or my mistake?
It should be noted that the similar code where the type Item is replaced by the concrete &() (see below process_units) works fine, as well as for the case when the lifetime for &() is defined but not bound for iter_units_extra_lt (passed into the original process) so the lifetime is required to be longer than in the original case:
fn process<IF, F, Item>(iter_func: IF, mut handler: F)
where
IF: FnOnce(&mut dyn FnMut(Item)),
F: FnMut(Item),
{
iter_func(&mut |item| handler(item))
}
// works with a concrete item type specified in the helper
fn process_units<IF, F>(iter_func: IF, mut handler: F)
where
IF: FnOnce(&mut dyn FnMut(&())),
F: FnMut(&()),
{
iter_func(&mut |item| handler(item))
}
// fails to compile with process()
fn iter_units(_func: &mut dyn FnMut(&())) {}
// fails to compile as well
#[allow(dead_code)]
fn iter_units2(_func: &mut dyn for<'a> FnMut(&'a ())) {}
// works with an extra lifetime
fn iter_units_extra_lt<'a>(_func: &mut dyn FnMut(&'a ())) {}
// works as well since there is no ref in item
fn iter_units_copied(_func: &mut dyn FnMut(())) {}
fn main() {
// fails to compile
// process(|func| iter_units(func), |_| ());
// fails to compile as well
// process(|func| iter_units2(func), |_| ());
// works with an extra lifetime
process(|func| iter_units_extra_lt(func), |_| ());
// works as well since there is no ref in item
process(|func| iter_units_copied(func), |_| ());
// works with a concrete item type specified in the helper
process_units(|func| iter_units(func), |_| ());
}
Broadly, the problem is that &() is not one type — it always has a lifetime, whether or not you write that lifetime explicitly. Then when functions get involved, dyn for<'a> FnMut(&'a ()) is a different type than dyn FnMut(&'b ()) (with 'b being declared somewhere outside). You have to pick which one you want to work with.
If you want dyn FnMut(&'b ()), then your iter_units_extra_lt is one possible solution.
If you want want dyn for<'a> FnMut(&'a ()), then process must have for<'a> in it:
Something that potentially makes this case more confusing is that the Fn traits and dyns have special syntactic sugar, where elided input lifetimes correspond to being higher ranked. So these two signatures are the same:
fn iter_units(_func: &mut dyn FnMut(&())) {}
fn iter_units2(_func: &mut dyn for<'a> FnMut(&'a ())) {}
// _func can accept any lifetime on its `&()` input
// (This is the only way it can accept local borrows within `iter_units*`)
But this one is different:
fn iter_units_extra_lt<'a>(_func: &mut dyn FnMut(&'a ())) {}
// _func can only accept a single lifetime `'a` on its `&()` input
// (every possible `'a` is at least just longer than the function call)
In contrast with how lifetime elision works with, say, bare references in function signatures (where elision introduces a new parameter on the function item).
// These two are the same
fn ex1(_: &mut &()) {}
fn ex2<'a>(_: &mut &'a ()) {}
Thanks for your useful suggestions!
It should be noted that iter_units_extra_lt has limited usage - unfortunately it is not suitable for a local object (for the function) to be passed by ref.
Your second variant having the explicit lifetime extracted from Item (IF: FnOnce(&mut dyn for<'a> FnMut(&'a Item))) can be only used for an actual type corresponding for Item not being another local ref as I understand. Though it is a partial solution, it really solves such concrete problem of passing a local object by ref from inside iter_units so your variant could be marked as solution.
The next question from the point - is it possible to write a generic helper process that can cover the both cases - passing a local object by value and by ref? Or even more advanced case of a nested refs like &&() etc?
I don't know for sure - is it necessary to create a separate topic for a more complex problem that was in the background of the original topic, marking the latter as solved, or is it better to continue the thread here?
Sometimes HRTBs -- F: for<'a> ... -- can end up requiring something to be 'static. But because &'a Item shows up in the inputs to the FnMut trait here, there's an implicit where Item: 'a constraint on the HRTB.
Is it enough for your actual use case? That I'm less sure about.
let local = "Hi".to_owned();
let mut vec = vec![&*local];
process(|_| {}, |item| vec.push(*item));
It is possible to generalize over "taking something owned or taking something borrowed", but such approaches tend to not be fully generic or to wreck inference. They resemble the general shape that is presented in the "alternative to GATs" article.
Thanks a lot for examples and links, I definitely need to read the materials thoroughly.
As for cases of Item that should not work, as I was trying to say, it might not be entirely clear - I meant a ref to an object that is local to iter_unit driver function rather local for a some scope enclosing process call that would narrow down the usage. When I had written 'static` I realized that some non-static sub refs can actually work - and that was then perfectly demonstrated by your examples. Counter examples for sub-refs that I had in mind:
fn iter_units(func: &mut dyn FnMut(&&())) {
// Item=&()
let x = ();
func(&&x)
}
It fails for the considered approach IF: FnOnce(&mut dyn FnMut(&Item)) and requires ref doubling: &&Item. Then we can consider &&&(), etc.
Personally my case is quite simple: need to handle owned and borrowed values that do not have additional refs hidden in Item. So I can just have two separate version of process to make them to work.
So it is more a matter of perfectionism and learning whether it is possible to combine the both cases in one function. So for more general cases with sub-refs.
Just realized that my personal case includes a bit more complex sample, something like Cow<'a, _> having ref inside the type rather external one like in &(). Nevertheless it could be still fit into iter_units_extra_lt approach, but requires the ref to outlive the driver (and helper) function call (to be used with process helper):
So it is still open problem how to pass something like Cow containing a borrow to a local object located inside the driver function (via a helper process generic over Item):
fn iter_local_cows(func: &mut dyn FnMut(Cow<str>)) {
let s = "Hi".to_owned();
func(s.as_str().into())
}
The both known approaches are not applicable for the case.