Home > database >  Difference between inline generics and where clauses?
Difference between inline generics and where clauses?

Time:07-29

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 interfaces 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.

  • Related