93

Before Rust 1.0, I could write a structure using this obsolete closure syntax:

struct Foo {
    pub foo: |usize| -> usize,
}

Now I can do something like:

struct Foo<F: FnMut(usize) -> usize> {
    pub foo: F,
}

But then what's the type of a Foo object I create?

let foo: Foo<???> = Foo { foo: |x| x + 1 };

I could also use a reference:

struct Foo<'a> {
    pub foo: &'a mut FnMut(usize) -> usize,
}

I think this is slower because

  1. the pointer dereference
  2. there's no specialization for the type of FnMut that actually ends up being used
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
bfops
  • 5,348
  • 5
  • 36
  • 48

2 Answers2

125

Complementing the existing answer with some more code for demonstration purposes:

Unboxed closure

Use a generic type:

struct Foo<F>
where
    F: Fn(usize) -> usize,
{
    pub foo: F,
}

impl<F> Foo<F>
where
    F: Fn(usize) -> usize,
{
    fn new(foo: F) -> Self {
        Self { foo }
    }
}

fn main() {
    let foo = Foo { foo: |a| a + 1 };
    (foo.foo)(42);
    
    (Foo::new(|a| a + 1).foo)(42);
}

Boxed trait object

struct Foo {
    pub foo: Box<dyn Fn(usize) -> usize>,
}

impl Foo {
    fn new(foo: impl Fn(usize) -> usize + 'static) -> Self {
        Self { foo: Box::new(foo) }
    }
}

fn main() {
    let foo = Foo {
        foo: Box::new(|a| a + 1),
    };
    (foo.foo)(42);
    
    (Foo::new(|a| a + 1).foo)(42);
}

Trait object reference

struct Foo<'a> {
    pub foo: &'a dyn Fn(usize) -> usize,
}

impl<'a> Foo<'a> {
    fn new(foo: &'a dyn Fn(usize) -> usize) -> Self {
        Self { foo }
    }
}

fn main() {
    let foo = Foo { foo: &|a| a + 1 };
    (foo.foo)(42);
    
    (Foo::new(&|a| a + 1).foo)(42);
}

Function pointer

struct Foo {
    pub foo: fn(usize) -> usize,
}

impl Foo {
    fn new(foo: fn(usize) -> usize) -> Self {
        Self { foo }
    }
}

fn main() {
    let foo = Foo { foo: |a| a + 1 };
    (foo.foo)(42);
    
    (Foo::new(|a| a + 1).foo)(42);
}

what's the type of a Foo object I create?

It's an unnameable, automatically generated type.

I could also use a reference [...] slower because [...] the pointer deref [...] no specialization

Perhaps, but it can be much easier on the caller.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Are any of these choices known to be speedier than the others? – DanielV Oct 07 '19 at 22:06
  • 2
    @DanielV yes, and no. If one of them was guaranteed to be universally better than the others, we wouldn't need the rest. The unboxed closure is probably the default choice and is likely to be the most performant, but the other ones can generate smaller code which might lead to them being more performant. – Shepmaster Oct 08 '19 at 01:37
  • 1
    rust really needs something like std::function, c++ uniform container for anything invokable. – pm100 Jul 25 '20 at 17:28
  • what does that "+ 'static" mean in the boxed trait one – pm100 Jul 25 '20 at 17:50
  • @pm100 [Why is adding a lifetime to a trait with the plus operator (Iterator + 'a) needed?](https://stackoverflow.com/q/42028470/155423); [The compiler suggests I add a 'static lifetime because the parameter type may not live long enough, but I don't think that's what I want](https://stackoverflow.com/q/40053550/155423); – Shepmaster Jul 27 '20 at 11:55
  • In the `Unboxed Closure` strategy is it possible to capture the struct which the closure is a member of ? – Ali Somay Oct 24 '20 at 07:47
  • 2
    @AliSomay Not to capture it, no, but you can pass it as an argument to the the closure itself in certain cases. See [How to use struct self in member method closure](https://stackoverflow.com/q/48717833/155423); [How can I modify self in a closure called from a member function?](https://stackoverflow.com/q/28597380/155423) – Shepmaster Oct 26 '20 at 15:27
  • 2
    What are advantages and disadvantages of each approach? – Konrad Jun 17 '21 at 09:34
  • for the last one in rust 1.65: expected fn pointer, found closure – theonlygusti Nov 25 '22 at 16:48
  • What if I want to pass a reference to a Foo to a function? What type should appear in the function signature? – Chuck Apr 03 '23 at 16:34
  • @Chuck You can [add `&` in the appropriate places](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1756e2bb2ce262e411c71b9a54c65ab0). This will use [higher ranked lifetimes](https://stackoverflow.com/q/35592750/155423). – Shepmaster Apr 14 '23 at 20:41
62

For what type you'd use in your third code snippet, there isn't one; closure types are anonymous and cannot be directly named. Instead, you'd write:

let foo = Foo { foo: |x| x + 1 };

If you're writing code in a context where you need to specify that you want a Foo, you'd write:

let foo: Foo<_> = Foo { foo: |x| x + 1 };

The _ tells the type system to infer the actual generic type for you.

The general rule of thumb as to which to use, in descending order:

  • Generic parameters: struct Foo<F: FnMut(usize) -> usize>. This is the most efficient, but it does mean that a specific Foo instance can only ever store one closure, since every closure has a different concrete type.
  • Trait references: &'a mut dyn FnMut(usize) -> usize. There's a pointer indirection, but now you can store a reference to any closure that has a compatible call signature.
  • Boxed closures: Box<dyn FnMut(usize) -> usize>. This involves allocating the closure on the heap, but you don't have to worry about lifetimes. As with a reference, you can store any closure with a compatible signature.

Before Rust 1.0

Closures that used the || syntax were references to closures stored on the stack, making them equivalent to &'a mut FnMut(usize) -> usize. Old-style procs were heap-allocated and were equivalent to Box<dyn FnOnce(usize) -> usize> (you can only call a proc once).

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
DK.
  • 55,277
  • 5
  • 189
  • 162
  • Is it possible to create functions like `compose` or `apply` that work generically over the 3 different traits `Fn`, `FnMut` and `FnOnce`? I find myself having to create 3 different versions of `compose` or `apply` to satisfy the 3 traits of closures. – CMCDragonkai Mar 30 '16 at 11:24
  • @CMCDragonkai If you need more details, you should ask a new question, but no, I don't believe it is. Keep in mind, however, that all `Fn`s can be used as `FnMut`s, and all `Fn`s and `FnMut`s can be used as `FnOnce`s. – DK. Mar 30 '16 at 12:01