【问题标题】:Receiving callbacks with `impl Trait` and passing them existential values使用 impl Trait 接收回调并传递它们存在的值
【发布时间】:2018-09-30 10:53:43
【问题描述】:

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

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

虽然我可以使用 impl Trait 参数语法或 fn c&lt;I: Iterator&lt;...&gt;&gt;(data: I) -&gt; () 轻松表达回调本身,但我不能接受它们,因为我的处理器在 I 上不是通用的,而是存在的。

我可以在I 上使处理部分通用,然后说I: Iterator&lt;Item = i32&gt;

/// 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&lt;...&gt; 这样的方法无法解决问题。

【问题讨论】:

  • PS:同样可以询问生命周期(“回调需要在 'a&amp;'a [u8] 参数中是通用的,但我的处理器选择生命周期,它只会勉强超过回调调用”)——我遇到了这两个版本,但对于类型系统,它们可能是相同的,一旦解决了不可命名的闭包版本,我只会继续解决问题的生命周期版本。
  • 我相信Generic struct over a generic type without type parameter的答案已经回答了您的问题。如果您不同意,请edit您的问题解释差异。否则,我们可以将此问题标记为已回答。
  • 我猜你不会觉得将签名更改为fn c(data: Box&lt;dyn Iterator&lt;Item=i32&gt;&gt;) ...
  • @Shepmaster,回答另一个问题的 RFC 在这里没有(直接)帮助,但帮助我找到了答案(正在进行中)。感谢您的链接和编辑。

标签: callback rust


【解决方案1】:

解决我的问题的方法是为回调引入一个特征,该特征具有在参数类型上通用的主力方法。这排除了直接将 lambdas 用作回调 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,但我不确定。)

【讨论】:

猜你喜欢
  • 2023-01-09
  • 2019-05-30
  • 1970-01-01
  • 2018-06-04
  • 1970-01-01
  • 2013-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多