How do I store a closure in Rust?

2019-01-11 17:40发布

问题:

I used to have something like:

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

Now the closure syntax is obsolete. I can do something like:

struct Foo<F: FnMut(uint) -> uint> {
  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(uint) -> uint
}

But I think it's slower because a) the pointer deref, and b) now there's no specialization for the type of FnMut that actually ends up being used.

回答1:

This is wrong: The direct equivalent would be Box<dyn FnMut(uint) -> uint>. This is effectively what the old syntax actually meant.

Correction: As dbaupp pointed out, that isn't correct. Old-style closures that used the || syntax were references to closures stored on the stack, making them equivalent to &'a mut FnMut(uint) -> uint. It was procs that were heap-allocated, and were equivalent to Box<dyn FnOnce(uint) -> uint> (you can only call a proc once). My apologies for the mistake.

As 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(uint) -> uint>. 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 FnMut(uint) -> uint. 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(uint) -> uint>. 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.


回答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,
}

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

Boxed trait object

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

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

Trait object reference

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

fn main() {
    let foo = Foo { foo: &|a| a + 1 };
    (foo.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:

  • How do I call a function through a member variable?
  • Returning a closure from a function
  • How to return an anonymous type from a trait method without using Box?
  • Closures as a type in a Rust struct
  • Types of unboxed closures being unique to each


标签: closures rust