在为处理程序回调(例如处理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()
}
这是不正确的,因为该实现在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()
}
一旦涉及到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<...>
之类的方法无法解决问题。
答案 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()
}
建议使用高级类型(as explained well in another question)作为解决方案,它有助于理解该问题,但似乎并不直接适用。 (它们可能有助于包装lambda,但我不确定。)