The Crius library provides
circuit-breaker-like functionality for Rust. Crius defines a struct called Command
which looks like this:
pub struct Command<P, T, CMD>
where
T: Send,
CMD: Fn(P) -> Result<T, Box<CommandError>> + Sync + Send,
{
pub config: Option<Config>,
pub cmd: CMD,
phantom_data: PhantomData<P>,
}
Is it possible to store an instance of Command
as a field in a different struct?
I started out trying to return a value of this type from a function. Simply instantiating the type is no problem:
/// This function constructs a simple instance of `Command<P, T, CMD>` with the
/// types set to:
///
/// P ~ u8
/// T ~ u8
/// CMD: Fn(u8) -> Result<u8, Box<CommandError>> + Send + Sync
///
/// This function compiles fine. However, there is no *concrete* type
/// for `CMD`. In compiler output it will be referred to as an
/// "anonymous" type looking like this:
///
/// Command<u8, u8, [closure@src/lib.rs:19:21: 19:38]>
fn simple_command_instance() {
let _ = Command::define(|n: u8| Ok(n * 2));
}
It becomes more difficult when writing a return type for the function:
fn return_command_instance() -> Command<u8, u8, ???> {
^
|
What goes here? -------
Command::define(|n: u8| Ok(n * 2))
}
The type inferred by the compiler is anonymous - it can't be put in
there. Many times when closures are passed around, people resort to
using a Box<F: Fn<...>>
, however there is no implementation for
impl Fn<T> for Box<Fn<T>>
- so boxing the type breaks the
constraints given by crius::command::Command
.
In versions of Rust that have the new impl Trait
feature (such as the
upcoming stable release), this is possible:
/// Use new `impl Trait` syntax as a type parameter in the return
/// type:
fn impl_trait_type_param() -> Command<u8, u8, impl Fn(u8) -> Result<u8, Box<CommandError>>> {
Command::define(|n: u8| Ok(n * 2))
}
This does not work in stable Rust and impl Trait
can only
be used in return types, not in struct members.
Trying to propagate the generic type ends up looking something like this:
fn return_cmd_struct<F>() -> Command<u8, u8, F>
where
F: Fn(u8) -> Result<u8, Box<CommandError>> + Send + Sync,
{
Command::define(|n: u8| Ok(n * 2))
}
But this does not compile:
error[E0308]: mismatched types
--> src/lib.rs:33:21
|
33 | Command::define(|n: u8| Ok(n * 2))
| ^^^^^^^^^^^^^^^^^ expected type parameter, found closure
|
= note: expected type `F`
found type `[closure@src/lib.rs:33:21: 33:38]`
Again, I don't know of a way to specify that concrete type in the result signature.
Even if propagating the type as a generic parameter worked, it would
still be an issue for our specific use-case. We want to store a
Command
as part of an actix actor which registers as a
SystemService
, which requires a Default
implementation, which
again eventually forces us to provide a concrete type.
If anyone has any ideas about possible ways to do this, please share them. Definitely knowing that it isn't possible would also be nice.
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.
Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure elsewhere to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they're defined.
Rust closures are harder for three main reasons: The first is that it is both statically and strongly typed, so we'll need to explicitly annotate these function types. Second, Lua functions are dynamically allocated ('boxed'.)
Keyword structA type that is composed of other types. Structs in Rust come in three flavors: Structs with named fields, tuple structs, and unit structs.
I currently know of no way a closure may be used as part of a return type other than using impl
or Box
, both of which you have mentioned and cannot be used in this situation.
An alternative would be to use a function pointer instead of a closure, like so:
fn return_command_instance() -> Command<u8, u8, fn(u8) -> Result<u8, Box<CommandError>>> {
Command::define(|n: u8| Ok(n * 2))
}
Notice the lower case fn
to signify a function pointer and not the trait Fn
. This is explained in more details in the chapter on Advanced Functions & Closures.
This will only work if you do not capture any variables in the function, if you do it will be compiled into a closure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With