Reputation: 1551
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]`
Upvotes: 2
Views: 2705
Reputation: 154846
The accepted answer perfectly addresses the solution for your use cases, but I'd like to clarify the "unsized" error message and the failure of the "simple example" to work.
Rust is quite capable of storing the closure in Instr
as defined in the question, but your type specification confused it. The type of each closure is anonymous, you cannot name it. Your attempt to specify the closure type by spelling out the trait Fn(ARGS...) -> RESULT
did the wrong thing because in Rust when you use a trait where a type is expected, it refers to the dynamic implementation of the trait, aka trait object. It's the trait object that is unsized and must be accessed via a reference or a smart pointer.
So, you can create an Instr
with an arbitrary closure in it, if you let Rust infer its type:
struct Instr<F>
where F: Fn([i32;4],[i32;3]) -> [i32;4]
{
name: String,
op: F
}
fn main()
{
// Simple example
let tmp : Instr<_> = Instr { name: "asd".to_string(), op: |a,b| a};
}
But this won't allow you to create a vector of Instr
s each with a different closure, because those Instr
s will have different types. For that you need to use a reference or a Box
as shown by the accepted answer.
Upvotes: 3
Reputation: 15105
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) }
];
}
Upvotes: 5