我有一个函数的特征,这个函数接受一个闭包作为参数,并且那个闭包需要一个参数,该参数需要是实现
Read
特征的某种类型:
trait CanRead {
type Reader: io::Read;
fn do_reading<F>(&mut self, fun: F)
where F: FnOnce(&mut Self::Reader);
}
通过指定
Read
并在 type Reader = Self;
函数中运行 fun(self);
,我可以轻松地为任何已经实现 do_reading
的东西实现此特征。
挑战是,我还想为某种必须创建
u8
的临时向量的类型实现此特征。那么关联类型Reader
需要是引用类型,但我不知道要给它什么生命周期:
pub struct EmptyStruct { }
impl CanRead for EmptyStruct {
type Reader = &[u8]; // doesn't compile; must specify a lifetime here
fn do_reading<F>(&mut self, fun: F)
where F: FnOnce(&mut Self::Reader) {
let temp = vec![1, 2, 3];
fun(&mut &temp[..]);
}
}
我知道我需要指定一个生命周期,但是它会是什么?我查看了这个有用的相关问题,但是建议的方法都不起作用。问题是
Reader
类型的生命周期实际上与 EmptyStruct
实例的生命周期无关;相反,Reader
引用类型只需要不超过对闭包本身的调用。有没有某种方法可以在 Rust 中指定这一点,或者有其他方法来解决这种模式?
playground 有我尝试过的方法,但没有成功。
(注意,我知道对于这个特定代码,temp
向量可以替换为静态数组,但这不适用于我真正需要做的事情。)
解决方法是使用动态分派,完全避免关联类型,如下所示:
pub trait CanRead {
fn do_reading<F>(&mut self, fun: F)
where F: FnOnce(&mut dyn io::Read);
}
pub struct EmptyStruct { }
impl CanRead for EmptyStruct {
fn do_reading<F>(&mut self, fun: F)
where F: FnOnce(&mut dyn io::Read) {
let temp = vec![1, 2, 3];
fun(&mut &temp[..]);
}
}
这里的缺点是,因为闭包的访问是通过&mut dyn
引用进行的,所以你会因为动态调度而损失一些运行时效率。当然,惩罚可能不会太大,但如果可能的话,最好使用 Rust 强大的类型系统和泛型来避免这种情况。
通用关联类型:
pub trait CanRead {
type Reader<'a>: io::Read where Self: 'a;
fn do_reading<F>(&mut self, fun: F)
where for<'a> F: FnOnce(&mut Self::Reader<'a>);
}
这里,关联类型Reader<'a>
采用生命周期参数,使其成为通用的。有一个生命周期界限
where Self: 'a
,说明参数的生命周期必须短于(或等于)实现
CanRead
的对象的生命周期(您可以将其读作“where
Self
outlives
'a
”) );这个生命周期界限是泛型关联类型的标准,因为它通常是简单的真实(实现
CanRead
的类型的方法不太可能在没有该类型值的情况下尝试使用
Reader
)并且使其更容易让编译器对程序进行推理。既然
Reader
是通用的,
do_reading
现在可以指定
fun
必须能够与读者的所有生命周期一起使用,而不仅仅是一个特定的生命周期:
for<'a>
意味着“对于所有生命周期
'a
” ”,因此
where
上的
do_reading
子句可以读作“其中
F
是一个
FnOnce
,可以处理对具有任意生命周期的
Self::Reader
的可变引用的参数”。 (好吧,所以
do_reading
可能需要
Self::Reader
的生命周期至少持续到
do_reading
调用的主体,但 Rust 会自动将声明解释为包含该要求。)有了
CanRead
的定义,就可以像这样实现
do_reading
:
impl CanRead for EmptyStruct {
type Reader<'a> = &'a [u8] where Self: 'a;
fn do_reading<F>(&mut self, fun: F)
where for<'a> F: FnOnce(&mut Self::Reader<'a>) {
let temp = vec![1, 2, 3];
fun(&mut &temp[..]);
}
}
这与你在操场上的基本相同 - 我必须更改 Reader
的定义和
do_reading
的签名以匹配该特征,但“明显”的代码在函数体内可以正常工作。