What is the difference generics and inline (I don't know what they are called) type definitions?
Example code:
fn main() {
c(|| {
println!("hello");
})
}
fn a<F>(f: F)
where
F: FnOnce() Send 'static,
{
f();
}
fn b<F: FnOnce() Send 'static>(f: F) {
f();
}
fn c(f: FnOnce() Send 'static) {
f();
}
Here a
and b
works fine but c
gives an error:
"trait objects must include the dyn
keyword"
If I add the dyn
keyword this time it gives another error: "the size for values of type (dyn FnOnce() Send 'static)
cannot be known at compilation time"
So can you explain me how the type declaration in c
is different?
CodePudding user response:
a
and b
are exactly equivalent. c
used to be allowed (but meant something different), but they've added a keyword to disambiguate. The equivalent using the new syntax would be
fn c(f: impl FnOnce() Send 'static)
{
f();
}
This is identical to a
and b
except that callers are not allowed to explicitly specify type annotations, so a::<SomeType>(x)
is allowed but c::<SomeType>(x)
is forbidden. This generally doesn't matter for FnOnce
since the type is likely to be a lambda type anyway that's impossible to spell out.
The compiler suggestion to put dyn
in changes the meaning.
fn c(f: &dyn FnOnce() Send 'static)
{
f();
}
This does not declare a generic function. It declares a function taking a trait object. In the generic function case, the type of the generic is known at compile time, which enables much better optimizations (often, FnOnce
arguments which are lambdas can be inlined entirely). dyn
is used when the type is not known at compile-time and more directly emulates Java-style interface
s with implementation hiding.
(I also had to add a &
since dyn
types are unsized and cannot be function arguments. You could also put it in a Box<dyn ...>
if ownership is required)
You should use generics instead of dyn
whenever possible. Of the three implementations a
, b
, and c
, they're all almost equivalent, save for being able to specify type arguments in the case of a
and b
. For FnOnce
and company, I'd use c
since we almost never want to specify arguments anyway. For other traits (FromIterator
is a big one where we frequently want to specify the type), I would probably favor a
or b
; which of those two is up to your preference. Those two are entirely equivalent.