Можно ли хранить структуру Rust, содержащую замыкание, в другой структуре? - PullRequest
0 голосов
/ 09 мая 2018

Библиотека Crius предоставляет функциональность, подобная выключателю, для Rust. Crius определяет структуру с именем Command, которая выглядит следующим образом:

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>,
}

Можно ли сохранить экземпляр Command как поле в другой структуре?

Я начал пытаться вернуть значение этого типа из функция. Простое создание экземпляра типа не проблема:

/// 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));
}

Это становится более трудным при написании типа возврата для Функция:

fn return_command_instance() -> Command<u8, u8, ???> {
                                                ^
                                                |
                          What goes here? -------

    Command::define(|n: u8| Ok(n * 2))
}

Тип, выведенный компилятором, является анонимным - его нельзя поместить в там. Много раз, когда замыкания проходят вокруг, люди прибегают к используя Box<F: Fn<...>>, однако реализации для impl Fn<T> for Box<Fn<T>> - так бокс типа ломает ограничения, заданные crius::command::Command.

В версиях Rust с новой функцией impl Trait (например, грядущая стабильная версия), это возможно:

/// 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))
}

Это не работает в стабильном Rust, и impl Trait может только использоваться в возвращаемых типах, а не в членах структуры.

Попытка распространения универсального типа выглядит примерно так это:

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))
}

Но это не компилируется:

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]`

Опять же, я не знаю способа указать этот конкретный тип в подпись результата.


Даже если распространение типа в качестве общего параметра сработало, это все еще будет проблемой для нашего конкретного варианта использования. Мы хотим хранить Command как часть actix актера, который регистрируется как SystemService, что требует реализации Default, которая снова в конечном итоге заставляет нас предоставить конкретный тип.

Если у кого-нибудь есть идеи о возможных способах сделать это, пожалуйста, поделитесь их. Определенно знать, что это не возможно, было бы неплохо.

1 Ответ

0 голосов
/ 10 мая 2018

В настоящее время я не знаю, каким образом закрытие может использоваться как часть типа возврата, кроме использования impl или Box, оба из которых вы упомянули и не могут использоваться в этой ситуации.

Альтернативой может быть использование указателя на функцию вместо замыкания, например:

fn return_command_instance() -> Command<u8, u8, fn(u8) -> Result<u8, Box<CommandError>>> {
    Command::define(|n: u8| Ok(n * 2))
}

Обратите внимание на нижний регистр fn для обозначения указателя функции, а не признака Fn. Это объясняется более подробно в главе Расширенные функции и замыкания .

Это будет работать, только если вы не захватите какие-либо переменные в функции, если вы это сделаете, это будет скомпилировано в замыкание.

...