Reputation: 741
This function returns a closure. Using impl Fn()
as the closure type is OK:
fn foo() -> impl Fn() {
|| ()
}
But this won't work:
fn foo<T: Fn()>() -> T {
|| ()
}
Nor this:
fn foo<T>() -> T
where
T: Fn(),
{
|| ()
}
Why do the last two examples not work?
Upvotes: 2
Views: 446
Reputation: 22476
Generics need to be specified by the caller. They cannot be specified from within your function implementation.
In general, there are four ways of returning a function in Rust:
impl Fn
, as you implemented it. This is the most common way.Box<dyn Fn>
. It has the advantage that it doesn't require an impl
, but apart from a couple of corner cases, this is rarely necessary.Just for completeness, here is the version with Box<dyn Fn>
:
fn foo() -> Box<dyn Fn()> {
Box::new(|| ())
}
And here is the version with a function pointer:
fn foo() -> fn() {
|| ()
}
impl Fn
is kind of like an internally-defined generic. The return value compiles to a real type, but all the caller has to know that this real type implements Fn
. It has very low overhead because the actual type is known to the compiler (even if it isn't known to the user) and it will be able to resolve calling its methods at compile time.Box<dyn Fn>
. Unlike impl Fn
, this is not a real type, but a trait object. The compiler looses the knowledge about the actual type behind it. All it knows is that it implements the trait Fn
, and calling its method will cause a runtime lookup in its vtable to call the correct method.fn
function pointer. Unlike dyn Fn
, which is a fat pointer with a vtable, this is just a normal thin pointer. Therefore it can't store any meta-information and can only hold functions that have no internal state. In the case of generics, this means it can only hold generics that don't capture any variables.Upvotes: 3
Reputation: 58725
In both of the "non-working" examples, it might be helpful to think about why T
is called a type parameter.
It's up to the caller of a function to determine its arguments, and it's the same with types. Sometimes they are specified explicitly and sometimes they are inferred, but it's always the caller that gets to say what they are and the implementation must work for all possible types. This is sometimes called universal quantification (in mathematics, ∀T
).
However, the type of the closure in your examples cannot be provided by the caller because it is an implementation detail of the function body. The type of the closure is fixed by the function and it doesn't make sense for the caller to specify a different type.
In your first example, it works because impl Fn()
in return position means exactly what is happening. That is, from the caller's perspective the concrete type is unknown but it's uniquely determined by the implementation. That's known as existentially quantified (in mathematics, ∃T
) - a type that we can be confident exists, though we can't necessarily know what it actually is.
Upvotes: 7
Reputation: 6071
This is so, because foo<T>
means that the caller chooses what type T
is. And since each closure has it's own type caller cannot pick it. This is exactly why impl Trait
was created for.
Upvotes: 3