Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

closures

rust

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
like image 383
bfops Avatar asked Jan 08 '15 02:01

bfops


People also ask

Are closures copy Rust?

A closure is Clone or Copy if it does not capture any values by unique immutable or mutable reference, and if all values it captures by copy or move are Clone or Copy , respectively.

How are closures implemented in Rust?

By taking &Fn(f64) , you've restricted yourself to only accepting closures which require immutable access to their context. If you want a closure to be able to mutate its context, you need to use FnMut instead.

How do I make an instance of a struct in Rust?

We create an instance by stating the name of the struct and then add curly brackets containing key: value pairs, where the keys are the names of the fields and the values are the data we want to store in those fields. We don't have to specify the fields in the same order in which we declared them in the struct.

Can structs have functions Rust?

Rust uses a feature called traits, which define a bundle of functions for structs to implement. One benefit of traits is you can use them for typing. You can create functions that can be used by any structs that implement the same trait.


2 Answers

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:

  • 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
  • Why does passing a closure to function which accepts a function pointer not work?
  • What does "dyn" mean in a type?
like image 98
Shepmaster Avatar answered Oct 08 '22 23:10

Shepmaster


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

like image 24
DK. Avatar answered Oct 09 '22 00:10

DK.