2

Why is that these 2 work:

fn apply_once1<F: FnOnce(T1) -> T2, T1, T2> (f: F, x: T1) -> T2 { f(x) } fn apply_once2<F, T1, T2> (f: F, x: T1) -> T2 where F: FnOnce(T1) -> T2 { f(x) } 

But this one doesn't compile:

fn apply_once3<T1, T2> (f: FnOnce(T1) -> T2, x: T1) -> T2 { f(x) } 

It complains of:

error: the trait `core::marker::Sized` is not implemented for the type `core::ops::FnOnce(T1) -> T2 + 'static` [E0277] once3<T1, T2> (f: FnOnce(T1) -> T2, x: T1) -> T2 { ^ help: see the detailed explanation for E0277 note: `core::ops::FnOnce(T1) -> T2 + 'static` does not have a constant size known at compile-time note: all local variables must have a statically known size 

I understand that FnOnce might not have statically known size, so normally I would fix that with & swapping the variable for a reference instead, so the size is now known. But I don't understand why apply_once1 and apply_once2 can get away with it?

Searching around, I can't find anything that talks about the difference between putting a trait bound on the argument versus putting it on the type variables.

2 Answers 2

3

Googling around, I can't find anything that talks about the difference between putting a trait bound on the argument versus putting it on the type variables.

This isn't actually what you're doing in the third one. Let's work with something simpler:

fn do_something<C: Clone>(x: C); fn do_something<C>(x: C) where C: Clone;

fn do_something(x: Clone)

The first two are actually the same thing, where is just syntactic sugar to make functions with non-trivial trait bounds easier to read, but both are saying "I am writing a function that will later be specialized for a type implementing the Clone trait".

The last one is saying

"I want x, which is literally the Clone trait."

Maybe that's confusing, let me elaborate, anything with angle brackets can be thought of as a schematic for a function, it's saying that for a given type satisfying certain requirements, the compiler can generate a function with the following code. This prevents us from writing:

fn do_something(x: f64); fn do_something(x: Vec<usize>);

And so on. Note the lack of angle brackets. If there are no angle brackets, you're not writing a schematic, you're telling the compiler the exact type you want. Now, Clone is a type... but it's a trait. Traits are not like structs. In fact, you were asking for a Trait Object, which can only be passed by reference since they're not sized.

You weren't actually asking for an object that implements FnOnce, you were actually asking for what's effectively the "platonic form" of FnOnce, which isn't something you can pass around, since it's more of an abstract concept than an actual thing. You can pass in &FnOnce, which is saying "a pointer to any random, arbitrary thing that happens to be an FnOnce", but that has some tradeoffs (I recommend reading the link about trait objects, or finding other SO answers on them, which covers them in more detail than is appropriate here).

So the short version: anything in the function signature is a concrete type, and anything in angle brackets (and/or a where clause) is a constraint on one of those concrete types.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks it makes a lot of sense now. It's like FnOnce is a typeclass constraint (using haskell terminology), and typeclass constraints are not used the same as a concrete type. Like f :: (Class a) => a -> b versus f :: Class a -> b. The second form wouldn't type check because Class has a kind of * -> Constraint, not a kind of *. Anyway, apply_once3 is therefore non-sensical. But there are other trait objects as you say where it does work like &FnMut, whereas &FnOnce doesn't work due to borrowing.
Yes, traits and type classes are very similar
2

When you use apply_once1, the type variable F is instantiated with a concrete function type of a known size.

In apply_once3, the FnOnce(T1) -> T2 is the type of a trait object, which doesn't have a known size, because a single instance of apply_once3 can be called with different functions.

See this section of the Rust book.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.