Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to store a Rust struct containing a closure in a different struct?

Tags:

rust

traits

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.

like image 832
tazjin Avatar asked May 09 '18 20:05

tazjin


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.

What is closure in Rust?

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.

Why Rust closures are somewhat hard?

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

What is struct Rust?

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.


Video Answer


1 Answers

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.

like image 94
Lukazoid Avatar answered Sep 19 '22 12:09

Lukazoid