我有一个特性,我想在其中提供一个方法。该方法是根据一些助手来实现的,这些助手在特性中没有业务,并且足够简单,动态多态性比使它们通用更有意义。所以我有的代码
fn use_trait(x: &Trait) {
println!("object says {}", x.needed());
}
trait Trait {
fn needed(&self) -> &str;
fn provided(&self) {
use_trait(self);
}
}
struct Struct();
impl Trait for Struct {
fn needed(&self) -> &str {
"Hello, world!"
}
}
fn main() {
Struct().provided();
}
然而,它没有编译,有错误:
error[E0277]: the trait bound `Self: std::marker::Sized` is not satisfied
--> <anon>:9:19
|
9 | use_trait(self);
| ^^^^ the trait `std::marker::Sized` is not implemented for `Self`
|
= help: consider adding a `where Self: std::marker::Sized` bound
= note: required for the cast to the object type `Trait`
我理解为什么——不能保证有人不会实现无大小类型的特性(从&T where T: Trait
转换为&Trait
需要T: Sized
,但声明不需要)。
然而,这些建议并不能满足我的需要。我可以添加
fn needed(&self) -> &str where Self: Sized
但是needed()
方法将不能在&Trait
上访问(因为Trait : ?Sized
),这会使事情变得无用,因为类型(做有用事情的实际类型)总是作为Arc<Trait>
处理。并添加
trait Trait: Sized
更糟的是,因为这根本不允许&Trait
(作为类型的Trait
是不定大小的,所以Trait
类型不实现特征Trait
)。
当然,我可以简单地制作
fn use_trait<T: Trait>(x: &T)
但在实际代码中,它背后有很多东西,所以我不希望出现单形化,尤其是因为在其他方面,特性总是作为特性对象处理。
有没有办法告诉Rust,impl Trait
的所有类型都必须进行大小调整,这里有一个方法的定义,应该适用于所有类型?
您需要在Trait
及其实现上添加一个as_trait
函数:
trait Trait {
fn needed(&self) -> &str;
fn provided(&self) {
use_trait(self.as_trait());
}
fn as_trait(&self) -> &Trait;
}
struct Struct();
impl Trait for Struct {
fn needed(&self) -> &str {
"Hello, world!"
}
fn as_trait(&self) -> &Trait {
self as &Trait
}
}
你可以在操场上试试。(特征对象)
@JoshuaEntrekin答案的增强版:
助手as_trait
函数可以放在一个辅助特性中,该特性为试图实现Trait
的所有Sized
类型获得一揽子实现。那么Trait
的实现者就不必做任何特殊的事情,转换就可以工作了。
fn use_trait(x: &Trait) {
println!("object says {}", x.needed());
}
trait Trait : AsTrait {
fn needed(&self) -> &str;
fn provided(&self) where Self : AsTrait {
use_trait(self.as_trait());
}
}
trait AsTrait {
fn as_trait(&self) -> &Trait;
}
impl<T : Trait + Sized> AsTrait for T {
fn as_trait(&self) -> &Trait { self }
}
struct Struct();
impl Trait for Struct {
fn needed(&self) -> &str {
"Hello, world!"
}
}
fn main() {
Struct().provided();
}
(正在播放)。
也可以简单地将provided
放在辅助特性中,但随后它将不得不不必要地动态调度到Self
的其他方法
更新:实际上,重点是仍然可以覆盖provided
。
现在,可以通过使其通用来进一步改进以上内容。存在std::makrer::Unsize,在撰写本文时它是不稳定的。我们无法制作
trait Trait : Unsize<Trait>
因为Rust不允许CRTP,但幸运的是,它足以对方法施加约束。所以
fn use_trait(x: &Trait) {
println!("object says {}", x.needed());
}
trait Trait {
fn needed(&self) -> &str;
fn provided(&self) where Self: AsObj<Trait> {
use_trait(self.as_obj());
}
}
trait AsObj<Tr: ?Sized> {
fn as_obj(&self) -> &Trait;
}
// For &'a Type for Sized Type
impl<Type: Trait> AsObj<Trait> for Type {
fn as_obj(&self) -> &Trait { self }
}
// For trait objects
impl AsObj<Trait> for Trait {
fn as_obj(&self) -> &Trait { self }
}
struct Struct();
impl Trait for Struct {
fn needed(&self) -> &str {
"Hello, world!"
}
fn provided(&self) {
println!("Aber dieses Objekt sagt Grüß Gott, Welt!"); // pardon my German, it is rusty.
}
}
fn main() {
let s: &Trait = &Struct();
s.provided();
}
(正在播放)
这最终使它对其他版本的实现者来说是透明的。
另请参阅此用户线程。