Reputation: 499
Is it possible to build the struct
struct MyFn<A, B> {
call: fn(A) -> B
}
such that it's able to capture immutable variables like in the following snippet?
let y: &f64 = &3.0
let my_fn: MyFn<f64, f64> {
call: |x| {
x + *y
}
}
I'm aware that a closure is needed in this case, but what should the type of call
be so that it can capture any immutable reference (e.g. a function reference)?
Upvotes: 2
Views: 404
Reputation: 154916
Is it possible to build the struct[...] such that it's able to capture...?
No - capturing involves a closure, and the type of MyFn::call
is a function. MyFn
can store a closure in call
by either making its type generic type or a trait object.
A generic is perfectly efficient, but makes MyFn
have a different type for each closure:
struct MyFn<A, B, F: Fn(A) -> B> {
call: F,
_data: PhantomData<(A, B)>,
}
let y: f64 = 3.0;
let my_fn: MyFn<f64, f64, _> = MyFn {
call: |x| x + y,
_data: PhantomData,
};
Having separate types for each closure means that you won't be able to easily create a vector of MyFn
s with different closures, or store it in a place where you need to spell out the type.
Alternatively, you can make call
dynamic, i.e. express it as a trait object. That will keep MyFn
generic only over the input and output type, and will prevent different closures from affecting the type of MyFn
. This flexibility comes at the cost of an allocation when constructing MyFn
and a pointer indirection when invoking call
. (That cost in advance is not nearly as large as it's sometimes made out to be, but it's good to be aware of it.) Such MyFn
looks like this:
struct MyFn<'a, A, B> {
call: Box<dyn Fn(A) -> B + 'a>,
}
let y: f64 = 3.0;
let my_fn: MyFn<f64, f64> = MyFn {
call: Box::new(|x| x + y),
};
The dynamic version appears to have an extra lifetime compared to the generic one, but that's because the lifetime was part of the unnamed type of the closure in the previous version.
Upvotes: 5