That's my case.
I need an enum structure because the closure is only one of several options.
So why does my code stop compiling trying to introduce type aliases ? What am I missing ?
That's my case.
I need an enum structure because the closure is only one of several options.
So why does my code stop compiling trying to introduce type aliases ? What am I missing ?
The code you've linked compiles. It produces quite a few warnings (all of which are just dead code or style problems), but it does compile.
I know, I need the commented version which turns the arg into a type alias ? And that makes it fail cause borrowing doesn't get released which I don't understand.
This compiles just fine with all lifetimes elided. Do you have a more concrete example? I'm not sure why exactly you need those.
I suspect this might be related to Higher-Rank Trait Bounds. I also simplified the playgroud example while still retaining the error: Rust Playground.
Note that you normally wouldn't want to "force" the user to use a boxed closure like that and instead use this function signature ef: impl Fn(&Person) -> bool
which works fine, but that doesn't answer the original question. I will investigate further.
EDIT:
This works: type EF2<T> = Box<dyn for <'a> Fn(&'a T) -> bool>; ... ef: EF2<Person>,
but it's not exactly ideal, since it "forces" the function to accept a reference. I will try to improve it.
Note that this is just the desugared form of type EF2<T> = Box<Fn(&T) -> bool>
.
Well, a reference actually gets my job done. Whether there is a version allowing T to be a value or reference would have been my next question. Anyway thank you very much.
Ok, the plot thickens. Look at this new playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cd003e074699c50a679abead539f562b It explains why the lifetime annotations are so "annoying" - they (kinda) have to be, otherwise the closure could "steal" the reference and keep it for longer than it is valid.
Let's look at use_fn
first. As someone mentioned, Box<dyn FnMut(&String) -> bool>
is syntax sugar for Box<dyn for<'a> FnMut(&'a String) -> bool>
which roughly means "the closure must be able to accept a string reference of any lifetime". That's why we can use it in the filter, but cannot "steal" the reference in the closure inside main
, because it would violate the "works with any lifetime" bound.
use_filter
, on the other hand, desugars into fn use_filter<'a>(_filter: Filter<&'a String>)
, which roughly means "caller can choose lifetime 'a
and give us a filter that accepts string references of lifetime 'a
". That's why we can "steal" the reference in main
- because we can choose the lifetime to be our lifetime. But this makes the filter basically uncallable inside use_filter
, as we can never assure that we give a reference that will live for at least 'a
, because we don't know what lifetime 'a
is going to be. Unless we give &'static String
, of course.
What we really need is somthing like filter: for<'a> Filter<&'a String>
, but I don't think that exists in Rust.
Finally, why do we need type Filter<'a, T> = Box<dyn FnMut(T) -> bool + 'a>;
instead of type Filter<T> = Box<dyn FnMut(T) -> bool>;
? Well, Box<dyn FnMut(T) -> bool>
is actually syntax sugar for Box<dyn FnMut(T) -> bool + 'static>
, otherwise we could never call the inner function. But the closure in main definitely isn't 'static
, as it captures a local variable. So use_filter
could "steal" the entire closure, put in into a global variable, for example, and then it could get called after the local variable it captures has been destroyed (big problem!). This is prevented by either:
'static
, which allows for the box to be stored and used indefinitely, but you then cannot access local variables from the closure that you want to put into that 'static
box.'a
into the box (instead of 'static
), which allows for the closure inside the box to capture local variables, but disallows the box to be stored and used indefinitely. At most, the box can be stored for the duration of 'a
.Finally, fn use_filter(_filter: Filter<&String>)
desugars to fn use_filter<'a>(_filter: Filter<'a, &String>)
, which means the caller can choose the lifetime 'a
, which main
can choose as "the lifetime of string_ref
" (roughly speaking)., which is why the local variable can be accesses from within the closure.
One more note: the "Box<dyn Trait>
is syntax sugar for Box<dyn Trait + 'static>
" is true in most cases (struct fields or type aliases for example), but there is one exeption: function arguments. In there, it's syntax sugar for a new lifetime that the function is generic in, so for example, fn use_fn(filter: Box<dyn FnMut(&String) -> bool>)
is syntax sugar for fn use_fn<'a>(filter: Box<dyn FnMut(&String) -> bool + 'a>)
. This means that the function cannot store the box indefinitely, but you can read from a local variable inside the boxxed closure, for example.
If I add another struct it fails again. How to tell Rust that the PeopleAndPerson struct's lifetime is limited to the inner loop? like people.borrow() was in previous example ?
This changes the semantics of the OP by requiring the boxed closure to be 'static
.
Here's a version that preserves semantics, though as others have noted, it's not idiomatic. It's probably nothing new in ilght of the other playgrounds.
// Whether you accept references with any lifetime can't be parameterized
// (This is the "higher-ranked type" part)
// vv
type EF<'a, T> = Box<dyn Fn(&T) -> bool + 'a>;
// So you just pass in `T` to the alias here
// v
pub fn execute_filter<T>(lambda: &EF<'_, T>, record: &T) -> bool {
// ...
}
fn filter_people<'ef>(
people: &People,
// I maintained lifetime equality here but there's no need to
// (Just showing how it would be done since one lifetime is under
// the alias and the other is not)
// vv vvv
ef: &'ef EF<'ef, Person>,
filtered_people: &mut Vec<usize>,
) {
But without further context, I'd write it like so. (Well, I wouldn't have execute_filter
, but close enough.)
Yeah, you can't parameterize being higher-ranked or not.
No, it's true in function arguments too. Annotations in the function body are the exception.
You need closures that work with PeopleAndPersonRef<'_>
for any lifetime, which means you can't let the caller choose the lifetime, and you can't equate the PeopleAndPerson<'?>
with a type parameter like the T
in enum EFEnum<'a, T>
. (Type parameters represent a single type, and types which differ by lifetime are different types.)
This works by removing generics and using PeopleAndPerson<'_>
directly.
Your actual use case may be running into something like the "can't parameterize being higher-ranked" restrictions mentioned above.
Instead of going more specific, let's try going more generic:
enum EFEnum<F> {
EFands(Vec<EFEnum<F>>),
EFLambda(F),
}
impl<F> EFEnum<F> {
fn filter<T>(&self, record: T) -> bool
where
T: Copy,
F: Fn(T) -> bool,
{
match self {
Self::EFands(conds) => conds.iter().all(|cond| cond.filter(record)),
Self::EFLambda(lambda) => lambda(record),
}
}
}
At the EFEnum
level, we don't need to worry about lifetimes or being higher ranked specifically. We just need a record
we can Copy
so we can pass it to all our closures, and we just need to make sure the things we own are closures that can take record
.
Then you state the higher-ranked requirement here, where you need it:
// N.b. I made `PeopleAndPersonRef<'_>: Copy` to avoid some unnecessary
// indirection, but you could have `F: Fn(&PeopleAndPersonRef<'_>)`
// instead if you wanted to
fn filter_people<F>(people: &People, ef: &EFEnum<F>, filtered_people: &mut Vec<usize>)
where
F: Fn(PeopleAndPersonRef<'_>) -> bool,
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.