用`impl Trait`接收回调并传递它们的存在性值

时间:2018-09-30 10:53:43

标签: callback rust

在为处理程序回调(例如处理shell命令或网络请求)定义API时,我想在回调签名中隐藏实现细节-例如,我想接受形式的回调

fn c(data: impl Iterator<Item = i32>) -> ()

尽管我可以轻松地使用impl Trait参数语法或将其表示为fn c<I: Iterator<...>>(data: I) -> ()来表示回调,但是我不能接受一般性的回调,因为我的处理器在I上不是通用的,但是存在的。

我可以使处理部分在I上通用,然后说I: Iterator<Item = i32>

/// A non-working callback that receives an iterator.
use std::iter::*;
use std::marker::PhantomData;

struct Processor<CB, I> {
    callback: CB,
    _i: PhantomData<I>,
}

// Here it'd be nice to say that it won't implement it for all I, but there exists an (unnamable) I
// for which it's implemented.
impl<CB, I> Processor<CB, I>
where
    CB: FnMut(I) -> (),
    I: Iterator<Item = i32>,
{
    fn process(self) {
        let a = [23, 42].iter().map(|i| i + 1);
        let mut cb = self.callback;
        cb(a)
    }
}

fn c(data: impl Iterator<Item = i32>) {
    println!("Data:");
    for i in data {
        println!("Item: {}", i);
    }
}

fn main() {
    let p = Processor {
        callback: c,
        _i: PhantomData,
    };
    p.process()
}

playground

这是不正确的,因为该实现在I上不是通用的,并且编译器对谎言表示不满:

error[E0308]: mismatched types
  --> src/main.rs:20:12
   |
20 |         cb(a)
   |            ^ expected type parameter, found struct `std::iter::Map`
   |
   = note: expected type `I`
              found type `std::iter::Map<std::slice::Iter<'_, {integer}>, [closure@src/main.rs:18:37: 18:46]>`

我还尝试了尽可能地命名该无法命名的名称,但这会产生非常笨拙的类型名称(在示例中可行,但在实际代码中会导致多行类型名称):

/// A non-working callback that receives an iterator.
use std::iter::*;

struct Processor<CB> {
    callback: CB,
}

impl<CB> Processor<CB>
where
    CB: FnMut(Map<std::slice::Iter<'static, i32>, FnOnce(i32) -> i32>) -> (),
{
    fn process(self) {
        let a = [23, 42].iter().map(|i| i + 1);
        let mut cb = self.callback;
        cb(a)
    }
}

fn c(data: impl Iterator<Item = i32>) {
    println!("Data:");
    for i in data {
        println!("Item: {}", i);
    }
}

fn main() {
    let p = Processor { callback: c };
    p.process()
}

playground

一旦涉及到lambda类型,此操作仍将失败:

error[E0277]: the size for values of type `(dyn std::ops::FnOnce(i32) -> i32 + 'static)` cannot be known at compilation time
  --> src/main.rs:8:1
   |
8  | / impl<CB> Processor<CB>
9  | | where
10 | |     CB: FnMut(Map<std::slice::Iter<'static, i32>, FnOnce(i32) -> i32>) -> (),
11 | | {
...  |
16 | |     }
17 | | }
   | |_^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::ops::FnOnce(i32) -> i32 + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-sized>
   = note: required by `std::iter::Map`

error[E0277]: the size for values of type `(dyn std::ops::FnOnce(i32) -> i32 + 'static)` cannot be known at compilation time
  --> src/main.rs:12:5
   |
12 | /     fn process(self) {
13 | |         let a = [23, 42].iter().map(|i| i + 1);
14 | |         let mut cb = self.callback;
15 | |         cb(a)
16 | |     }
   | |_____^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::ops::FnOnce(i32) -> i32 + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-sized>
   = note: required by `std::iter::Map`

解决此问题的惯用方法是什么?是否有可能使用另一种语法表示“对于所有CB,它实现了Processor,其中CB类型需要对其参数进行泛型,然后我才能选择其类型”?像

impl<CB> Processor<CB>
where
    CB: FnMut(impl Iterator<Item = i32>) -> (),

鉴于我的目标是嵌入式环境,因此我正在寻找不涉及std的解决方案,因此Box<...>之类的方法无法解决问题。

1 个答案:

答案 0 :(得分:0)

解决我问题的方法是为回调引入特征,该特征具有对参数类型通用的主力方法。这就排除了将lambda直接用作AFAICT回调(我不能包装它们),但是允许传递关于其参数类型或生存期通用的函数。

最重要的是特征函数

fn call_now<T: Iterator<Item=i32>>(&mut self, data: T) -> ();

,完整的代码示例如下:

use std::iter::*;

trait Callback {
    fn call_now<T: Iterator<Item=i32>>(&mut self, data: T) -> ();
}

struct Processor<CB>
{
    callback: CB
}

impl<CB: Callback> Processor<CB> where
{
    fn process(&mut self) {
        let a = [23, 42].iter().map(|i| i + 1);
        self.callback.call_now(a);
    }
}

struct C();
impl Callback for C {
    fn call_now<T: Iterator<Item=i32>>(&mut self, data: T) {
        println!("Data:");
        for i in data {
            println!("Item: {}", i);
        }
    }
}

fn main() {
    let mut p = Processor {
        callback: C(),
    };
    p.process()
}

try on playground

建议使用

高级类型(as explained well in another question)作为解决方案,它有助于理解该问题,但似乎并不直接适用。 (它们可能有助于包装lambda,但我不确定。)