Search code examples
genericsrustclosurestraitstrait-objects

How can I store a closure object in a struct?


I can't figure out how to store a closure object in a struct. The arguments and return for the closure object are known. Here's my condensed code:

struct Instr<F>
    where F: Fn([i32;4],[i32;3]) -> [i32;4]
{
    name: String,
    op: F
}

fn main()
{
    // Simple example showing the difficulty:
    let tmp : Instr<Fn([i32;4],[i32;3]) -> [i32;4]> = Instr { name: "asd".to_string(), op: |a,b| a};

    // What I really want is something more like this:
    // let instrs = vec![
    //     Instr { name: "asdf", op: |a,b| a },
    //     Instr { name: "qwer", op: |a,b| a }
    // ];
}

Frankly I don't understand what any of these errors mean. In my mind this is simple. The closure has a type, and a known size. It should be straightforward to store it in a typed field of the same type. Right?

Attempting to add F: ?Sized as the error messages suggest does not fix the "size not known at compile time" errors.

Could someone help me get this compiling correctly?

error[E0277]: the size for values of type `dyn Fn([i32; 4], [i32; 3]) -> [i32; 4]` cannot be known at compilation time
  --> a.rs:11:15
   |
1  | struct Instr<F>
   |              - required by this bound in `Instr`
...
11 |     let tmp : Instr<Fn([i32;4],[i32;3]) -> [i32;4]> = Instr { name: "asd".to_string(), op: |a,b| a};
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `dyn Fn([i32; 4], [i32; 3]) -> [i32; 4]`
help: you could relax the implicit `Sized` bound on `F` if it were used through indirection like `&F` or `Box<F>`
  --> a.rs:1:14
   |
1  | struct Instr<F>
   |              ^ this could be changed to `F: ?Sized`...
2  |     where F : Fn([i32;4],[i32;3]) -> [i32;4]
   |           - ...if indirection was used here: `Box<F>`
...
5  |     op : F
   |          - ...if indirection was used here: `Box<F>`

error[E0277]: the size for values of type `dyn Fn([i32; 4], [i32; 3]) -> [i32; 4]` cannot be known at compilation time
  --> a.rs:11:55
   |
1  | / struct Instr<F>
2  | |     where F : Fn([i32;4],[i32;3]) -> [i32;4]
3  | | {
4  | |     name : String,
5  | |     op : F
6  | | }
   | |_- required by `Instr`
...
11 |       let tmp : Instr<Fn([i32;4],[i32;3]) -> [i32;4]> = Instr { name: "asd".to_string(), op: |a,b| a};
   |                                                         ^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `dyn Fn([i32; 4], [i32; 3]) -> [i32; 4]`

error[E0308]: mismatched types
  --> a.rs:11:92
   |
11 |     let tmp : Instr<Fn([i32;4],[i32;3]) -> [i32;4]> = Instr { name: "asd".to_string(), op: |a,b| a};
   |                                                                                            ^^^^^^^ expected trait object `dyn Fn`, found closure
   |
   = note: expected trait object `dyn Fn([i32; 4], [i32; 3]) -> [i32; 4]`
                   found closure `[[email protected]:11:92: 11:99]`

Solution

  • Different closures have different sizes, so you can't store "raw closures" or "raw trait objects" in structs, they must be behind a pointer, so you can place them in a Box like so:

    struct Instr {
        name: String,
        op: Box<dyn Fn([i32; 4], [i32; 3]) -> [i32; 4]>,
    }
    
    fn main() {
        let instrs = vec![
             Instr { name: "asdf".into(), op: Box::new(|a,b| a) },
             Instr { name: "qwer".into(), op: Box::new(|a,b| a) }
         ];
    }
    

    playground