MaPo
MaPo

Reputation: 771

How can I define a trait with arguments and return types like Fn?

I'm confused by the following code (Listing 13-9):

struct Cacher<T>
where
    T: Fn(i32) -> i32,
{
    calculation: T,
    value: Option<i32>,
}

I understand that Fn is a trait, but usually a trait has no argument and returned type. How can I define a trait like Fn?

I tried to look at the definition (actually it's FnOnce, but Fn has FnMut bound and FnMut has FnOnce bound...), but I'm still confused. What is the meaning of that <Args>? Then also something written about it in the Nomicon; but I do not understand it:

Where Fn(a, b, c) -> d is itself just sugar for the unstable real Fn trait

Upvotes: 5

Views: 885

Answers (1)

Shepmaster
Shepmaster

Reputation: 430634

How can I define a trait with arguments and return types like Fn?

If you mean the syntax MyTrait(A) -> B, you cannot. Traits with "arguments" and "return types" are special and are restricted to the Fn, FnMut and FnOnce traits. This is hard-coded into the compiler. There's even a specific error message for it:

error: parenthetical notation is only stable when used with `Fn`-family traits (see issue #29625)
 --> src/main.rs:5:8
  |
5 |     A: MyTrait(A) -> B,
  |        ^^^^^^^^^^^^^^^

That being said, this syntax desugars into the standard trait syntax. You can see what FnOnce is from the docs:

pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

The compiler converts Fn(A, B, C) -> Z into Fn<(A, B, C), Output = Z>. Args is a standard trait generic type parameter and Output is a standard associated type. The "rust-call" ABI is some internal compiler machinery that makes this a bit more efficient and can be ignored most of the time.

You are completely allowed to create your own traits with generic parameters and associated types. You just are not allowed to use the parenthetical notation.

Upvotes: 9

Related Questions