How do I store a closure in Rust?

2019-01-11 18:01发布

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.

标签: closures rust
2条回答
疯言疯语
2楼-- · 2019-01-11 18:22

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:

查看更多
小情绪 Triste *
3楼-- · 2019-01-11 18:42

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.
查看更多
登录 后发表回答