提供的方法将 &自身转换为特征对象



我有一个特性,我想在其中提供一个方法。该方法是根据一些助手来实现的,这些助手在特性中没有业务,并且足够简单,动态多态性比使它们通用更有意义。所以我有的代码

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();
}

(正在播放)

这最终使它对其他版本的实现者来说是透明的。

另请参阅此用户线程。

最新更新