【发布时间】:2018-09-30 10:53:43
【问题描述】:
在为处理程序回调定义 API 时(例如处理 shell 命令或网络请求),我想从回调签名中隐藏实现细节——例如,我想接受表单的回调
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<...> 这样的方法无法解决问题。
【问题讨论】:
-
PS:同样可以询问生命周期(“回调需要在
'a的&'a [u8]参数中是通用的,但我的处理器选择生命周期,它只会勉强超过回调调用”)——我遇到了这两个版本,但对于类型系统,它们可能是相同的,一旦解决了不可命名的闭包版本,我只会继续解决问题的生命周期版本。 -
我相信Generic struct over a generic type without type parameter的答案已经回答了您的问题。如果您不同意,请edit您的问题解释差异。否则,我们可以将此问题标记为已回答。
-
我猜你不会觉得将签名更改为
fn c(data: Box<dyn Iterator<Item=i32>>)... -
@Shepmaster,回答另一个问题的 RFC 在这里没有(直接)帮助,但帮助我找到了答案(正在进行中)。感谢您的链接和编辑。