But function is not satisfy this, we should not think the function itself is illegal, but should think to the return value is the generic type.
These functions do exist and reasonable, because the output is associated with type, we can abstract it, this is a design error, because of its illegal judgment is given.
When you write this function, you will get a compiler error.
fn a<F: for<'s> Fn() -> Vec<&'s i32>>(f: F)
error[E0582]: binding for associated type `Output` references lifetime `'s`, which does not appear in the trait input types
He requires that the higher-order lifecycle cannot only appear in the associated type, but must appear in the generic parameter. (Calling generics as input types is confusing, but let's ignore this little annoyance now)
This is reasonable, because the association type of the feature is unique to the implementation, so the user must not give a type that meets this condition. It is reasonable to expose the problem as early as possible.
However, because the function output is of association type, we will get this E0582.
I would argue that being able to assume the output type of a Fn type is constrained is much more useful than being able to abstract over those functions. How often do you even want to abstract over them?
That said I think it's a bit too late to change this. There's a lot of code that assumes that the return type of FnOnce is an associated type, and thus constrained, and changing this would break all that code.
It's a breaking change. If the output can vary, the R in the implementation below would have multiple possibilities. Supporting extensions like this was one of the motivations of the RFC.
trait FnExt<A>: Fn(A) -> <Self as FnExt<A>>::Out {
type Out;
}
impl<A, R, F: Fn(A) -> R> FnExt<A> for F {
type Out = R;
}
Further thoughts that go vaguely in the direction of "if we get a second set of closure traits, they should probably use something GAT-like and not input type parameters"
It would probably wreak havoc on various types of inference (particularly around Fn trait extensions). Based on some experiences with them -- I'll refer back to this later.
You can't have closures that capture things and return values to them; you can't have custom Fn-like implementers that do something like this.
struct S(String);
impl FnMut<()> for S {
fn call_mut(&mut self, _: ()) -> &str {
self.0.push_str(".");
&self.0
}
}
But there's probably no acceptable solution without whole-new traits, whether it's GATs or making the output type a trait input parameter, because the pattern can't be supported by FnOnce (so you'd have to break the supertrait connection).
struct S(String);
impl<'a> FnMut<(), &'a str> for S {
// Output type ^^^^^^^
fn call_mut(&mut self, _: ()) -> &'a str {
self.0.push_str(".");
&self.0
}
}
impl<'a> FnOnce<(), &'a str> for S {
// Output type ^^^^^^^
fn call_once(&self, _:()) -> &'a str {
// What here? :-(
}
}
Arguably this is only a problem if the traits are stabilized, but presumably that's still a goal.
Here's another downside. It's not uncommon for people to want to generalize over owned-or-borrowing closures; there's a question along those lines every month or so on URLO. My first playground link above is such an example. Here's another:
impl S {
fn choose_randomly<It, F: FnMut(&str) -> It>(&self, chooser: F) -> It {
// Roll of dice ha ha
chooser(&self.0[4])
}
}
// Fails because the return of `trim` is not a singular type, so it can't
// resolve to `It`
s.choose_randomly(str::trim)
There are workarounds... albeit they can have inference issues, like in the first playground.
impl S {
fn choose_randomly<F>(&self, mut chooser: F)
-> <F as FnOnce<(&str,)>>::Output
where
F: for<'a> FnMut<(&'a str,)>
This works because you don't have to mention the associated type as a type parameter, thus making it resolve to a single type. GATs for FnMut and Fn-like traits could preserve that, but input type parameters won't (unless we get generic type constructors I guess, impl<F<*>, ...>).
The playground shows one side of the variance; the other side is that anything that can meet your non-compiling bound has to support the 'static case. ↩︎
It is important to ensure correctness. If a thing may be incorrect, at least we should try to make efforts first. If we notice the difference and can't help it, at least mention it in the document.
Yes, this is my fault, but there are always types of life cycle unchanged.
That's why I came here,
one type is more general than the other
fn adopts a similar structure to FnOnce, so this is one of the sequelae caused by the added restrictions in the repair of #32330.
Essentially, the change is that each time you reference foo (directly!), there is now a single lifetime assigned for its parameter 'a , no matter how many times you call it. In cases like bar above, we need to assign distinct references to each call, so we have to reference foo twice.
Don't be so pessimistic. Changing an association type back to a generic type has essentially relaxed the restrictions, and it is not difficult to be compatible with it.
the type parameter `R` is not constrained by the impl trait, self type, or predicates
As long as the output is generic, this error will be suppressed.
(Maybe this is what you meant by your edits, but) it can't be suppressed because this scenario is possible.
impl<A> MyFn<A, String> for S {}
impl<A> MyFn<A, i32> for S {}
impl<A, R, F: MyFn<A, R>> MyFnExt<A> for F {
type Out = R; // Is `R` `String` or `i32` for `F = S`?
}
You are right. This is really a breaking change. For the new function trait, the previous assumption that the return depends on the input is invalid. The code based on this will no longer work.
But now, I just want to let us regain the complete abstraction of the function. If it is not compatible with the old function characteristics, implementing new NewFnOnce trait for closures and function pointers independently may solve this problem.
The function pointer does not have this ::Output usage, and its modification should be harmless.
hr_lifetime_in_assoc_type It's the best time we can salvage it, but we missed it, so if we want to abstract a constructor, we have to manually abstract the independent trait to implement it.It's cumbersome, but it works.