How do I store a closure in a struct in Rust?

31,679

Solution 1

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

Solution 2

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:

Share:
31,679
bfops
Author by

bfops

Updated on July 05, 2022

Comments

  • bfops
    bfops almost 2 years

    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
  • CMCDragonkai
    CMCDragonkai about 8 years
    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.
  • DK.
    DK. about 8 years
    @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 Fns can be used as FnMuts, and all Fns and FnMuts can be used as FnOnces.
  • DanielV
    DanielV over 4 years
    Are any of these choices known to be speedier than the others?
  • Shepmaster
    Shepmaster over 4 years
    @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.
  • pm100
    pm100 almost 4 years
    rust really needs something like std::function, c++ uniform container for anything invokable.
  • pm100
    pm100 almost 4 years
    what does that "+ 'static" mean in the boxed trait one
  • Shepmaster
    Shepmaster almost 4 years
  • Ali Somay
    Ali Somay over 3 years
    In the Unboxed Closure strategy is it possible to capture the struct which the closure is a member of ?
  • Shepmaster
    Shepmaster over 3 years
    @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; How can I modify self in a closure called from a member function?
  • Konrad
    Konrad almost 3 years
    What are advantages and disadvantages of each approach?