我可以强制特征是协变的吗?
Can I force a trait to be covariant?
感谢@francis-gagné 对另一个问题的 ,我对方差的工作原理有了更清晰的认识。例如,包含引用的类型在其生命周期参数上是协变的,如下所示。
struct Foo<'a> (PhantomData<&'a str>);
/// Foo is covariant over its lifetime parameter
pub fn test_foo<'a:'b, 'b:'c, 'c>() {
let fa: Foo<'a> = Foo(PhantomData);
let fb: Foo<'b> = Foo(PhantomData);
let fc: Foo<'c> = Foo(PhantomData);
let v: Vec<Foo<'b>> = vec![fa, fb]; // fc is not accepted
}
另一方面,接受引用(或包含引用的类型)的函数在其生命周期参数上是逆变的。
struct Bar<'a> (PhantomData<fn(&'a str)>);
/// Bar is contravariant over its lifetime parameter
pub fn test_bar<'a:'b, 'b:'c, 'c>() {
let ba: Bar<'a> = Bar(PhantomData);
let bb: Bar<'b> = Bar(PhantomData);
let bc: Bar<'c> = Bar(PhantomData);
let v: Vec<Bar<'b>> = vec![bb, bc]; // ba is not accepted
}
最后,具有生命周期参数的特征在其生命周期参数内是不变的。
pub trait Baz<'a> {}
impl<'a> Baz<'a> for () {}
/// Baz is invariant over its lifetime parameter
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let za: Box<dyn Baz<'a>> = Box::new(());
let zb: Box<dyn Baz<'b>> = Box::new(());
let zc: Box<dyn Baz<'c>> = Box::new(());
let v: Vec<Box<dyn Baz<'b>>> = vec![zb]; // za and zx are not accepted
}
这是有道理的,因为特征可以通过协变和逆变类型来实现,如下图所示。
impl<'a> Baz<'a> for Foo<'a> {}
impl<'a> Baz<'a> for Bar<'a> {}
我的问题是:我可以强制特征在其生命周期参数内保持协变吗?我希望有一个标记特征,例如:
trait Baz<'a>: Covariant<'a> {}
这将使使用逆变类型实现该特征是非法的,并允许 za
成为上述 test_baz
函数中向量 v
的成员。
当然,能够做相反的事情(强制特征逆变)也很有用...
没有
你可以表达"a value that implements Baz<'x>
for any 'x
":
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let za: Box<dyn for<'x> Baz<'x>> = Box::new(());
let zb: Box<dyn for<'x> Baz<'x>> = Box::new(());
let zc: Box<dyn for<'x> Baz<'x>> = Box::new(());
let v: Vec<Box<dyn for<'x> Baz<'x>>> = vec![za, zb, zc];
}
但是你不能(从 Rust 1.31 开始)写 Box<dyn for<'x: 'b> Baz<'x>>
,即使你可以,这种语法也只能在生命周期内有效;它不会让你表达类型参数的协方差。
我找到了解决方法。我没有将特征标记为协变(正如@trentcl 指出的那样,这在 Rust 1.31 中是不可能的),而是让类型在所有小于其自身的生命周期内实现特征:
impl<'a:'b, 'b> Baz<'b> for Foo<'a> {}
这样,只要需要 Bar<'b>
,我就可以使用 Foo<'b>
和 Foo<'a>
的实例:
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let fa: Foo<'a> = Foo(PhantomData);
let fb: Foo<'b> = Foo(PhantomData);
let fc: Foo<'c> = Foo(PhantomData);
let v: Vec<&dyn Baz<'b>> = vec![&fa, &fb]; // &fc is not accepted
}
当然,这需要特征的每个实现者都遵循这个模式,所以它不如将特征本身标记为协变那么强大。但在某些情况下它可以解决问题。
感谢@francis-gagné 对另一个问题的
struct Foo<'a> (PhantomData<&'a str>);
/// Foo is covariant over its lifetime parameter
pub fn test_foo<'a:'b, 'b:'c, 'c>() {
let fa: Foo<'a> = Foo(PhantomData);
let fb: Foo<'b> = Foo(PhantomData);
let fc: Foo<'c> = Foo(PhantomData);
let v: Vec<Foo<'b>> = vec![fa, fb]; // fc is not accepted
}
另一方面,接受引用(或包含引用的类型)的函数在其生命周期参数上是逆变的。
struct Bar<'a> (PhantomData<fn(&'a str)>);
/// Bar is contravariant over its lifetime parameter
pub fn test_bar<'a:'b, 'b:'c, 'c>() {
let ba: Bar<'a> = Bar(PhantomData);
let bb: Bar<'b> = Bar(PhantomData);
let bc: Bar<'c> = Bar(PhantomData);
let v: Vec<Bar<'b>> = vec![bb, bc]; // ba is not accepted
}
最后,具有生命周期参数的特征在其生命周期参数内是不变的。
pub trait Baz<'a> {}
impl<'a> Baz<'a> for () {}
/// Baz is invariant over its lifetime parameter
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let za: Box<dyn Baz<'a>> = Box::new(());
let zb: Box<dyn Baz<'b>> = Box::new(());
let zc: Box<dyn Baz<'c>> = Box::new(());
let v: Vec<Box<dyn Baz<'b>>> = vec![zb]; // za and zx are not accepted
}
这是有道理的,因为特征可以通过协变和逆变类型来实现,如下图所示。
impl<'a> Baz<'a> for Foo<'a> {}
impl<'a> Baz<'a> for Bar<'a> {}
我的问题是:我可以强制特征在其生命周期参数内保持协变吗?我希望有一个标记特征,例如:
trait Baz<'a>: Covariant<'a> {}
这将使使用逆变类型实现该特征是非法的,并允许 za
成为上述 test_baz
函数中向量 v
的成员。
当然,能够做相反的事情(强制特征逆变)也很有用...
没有
你可以表达"a value that implements Baz<'x>
for any 'x
":
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let za: Box<dyn for<'x> Baz<'x>> = Box::new(());
let zb: Box<dyn for<'x> Baz<'x>> = Box::new(());
let zc: Box<dyn for<'x> Baz<'x>> = Box::new(());
let v: Vec<Box<dyn for<'x> Baz<'x>>> = vec![za, zb, zc];
}
但是你不能(从 Rust 1.31 开始)写 Box<dyn for<'x: 'b> Baz<'x>>
,即使你可以,这种语法也只能在生命周期内有效;它不会让你表达类型参数的协方差。
我找到了解决方法。我没有将特征标记为协变(正如@trentcl 指出的那样,这在 Rust 1.31 中是不可能的),而是让类型在所有小于其自身的生命周期内实现特征:
impl<'a:'b, 'b> Baz<'b> for Foo<'a> {}
这样,只要需要 Bar<'b>
,我就可以使用 Foo<'b>
和 Foo<'a>
的实例:
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let fa: Foo<'a> = Foo(PhantomData);
let fb: Foo<'b> = Foo(PhantomData);
let fc: Foo<'c> = Foo(PhantomData);
let v: Vec<&dyn Baz<'b>> = vec![&fa, &fb]; // &fc is not accepted
}
当然,这需要特征的每个实现者都遵循这个模式,所以它不如将特征本身标记为协变那么强大。但在某些情况下它可以解决问题。