• 实现了Rust Trait的类型,那么该类型的引用也实现了trait吗?
  • 发布于 2个月前
  • 279 热度
    0 评论
如果你在一个类型上实现了一个trait,然后引用了这个类型,那么类型的引用也实现了这个trait吗?有一段时间我是这么想的!但实际上并不是,有时候Rust为你做的事情可能会混淆幕后真正发生的事情。

为了演示,让我从一个名为Speaker的trait和一个实现该trait的空struct开始:
/// 定义一个trait,有一个speak方法。
trait Speaker {
    fn speak(&self);
}

/// BasicSpeaker是一个空结构体,只是为了实现Speaker。
struct BasicSpeaker;

/// BasicSpeakers实现speak方法
impl Speaker for BasicSpeaker {
    fn speak(&self) {
        println!("Hello!");
    }
}
现在,在main函数中,以下代码应该可以工作:
// 创建BasicSpeaker结构体
let speaker = BasicSpeaker;

// 调用在BasicSpeaker上定义的speak方法
speaker.speak();
确实如此,它就会输出“Hello!”。

如果我引用了一个BasicSpeaker,我仍然可以对它调用speak,因为Rust会自动解除对变量的引用。所以下面的代码也可以工作:
// 堆代码 duidaima.com
// 定义一个BasicSpeaker的引用
let speaker_ref: &BasicSpeaker = &speaker;

// 通过引用调用在BasicSpeaker上定义的speak方法
speaker_ref.speak();
这可能会让你认为BasicSpeaker实现了Speaker,引用&BasicSpeaker也实现了Speaker。也许是Rust的魔法?让我们更具体地测试一下,定义一个接受impl Speaker类型形参的函数。
fn speak_to(s: impl Speaker) {
    s.speak();
}

fn main() {
    // 创建BasicSpeaker结构体
    let speaker = BasicSpeaker;

    // 将speaker传递给新函数
    speak_to(speaker);
}
这是可行的,因为BasicSpeaker实现了Speaker特性。让我们尝试同样的事情,但这次是传递BasicSpeaker的引用:
// 定义一个BasicSpeaker的引用
let speaker_ref: &BasicSpeaker = &speaker;

// 将引用传递给' speak_to '
speak_to(speaker_ref);
这行不通!错误信息如下所示:
error[E0277]: the trait bound `&BasicSpeaker: Speaker` is not satisfied
  --> src/main.rs:31:14
   |
31 |     speak_to(speaker_ref);
   |     -------- ^^^^^^^^^^^ the trait `Speaker` is not implemented for `&BasicSpeaker`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `Speaker` is implemented for `BasicSpeaker`
note: required by a bound in `speak_to`
  --> src/main.rs:16:21
   |
16 | fn speak_to(s: impl Speaker) {
   |                     ^^^^^^^ required by this bound in `speak_to`

For more information about this error, try `rustc --explain E0277`.
最初的错误消息是模糊的,但是第一个代码块旁边的消息更清晰:“&BasicSpeaker没有实现trait Speaker”。前面的代码示例演示了你可以在引用上调用没有在该引用上实现的方法,因为Rust会默默地为你解引用该值。Rust是这样做的:
// Rust将' speaker_ref.speak() '转换为
(*speaker_ref).speak();
这并不意味着&BasicSpeaker(一个引用)实现了Speaker。

直接的解决方案
最直接的解决方案是在BasicSpeaker的引用上实现Speaker,如下所示:
impl Speaker for &BasicSpeaker {
    fn speak(&self) {
        println!("Hello!");
    }
}
将其添加到代码中后,就可以编译和运行了。所以这是一种解决方案,但这并不理想。首先,这基本上是先前实现的重复代码。下面是一个稍微改进的实现,它只调用底层结构体的方法:
impl Speaker for &BasicSpeaker {
    fn speak(&self) {
        return (**self).speak();
    }
}
很明显,我必须对self进行两次解引用,因为该函数接受&self,而self是&BasicSpeaker。这意味着参数是一个&&BasicSpeaker,必须对其进行两次解引用才能获得实现speak()的BasicSpeaker。

好了,现在没有那么多代码复制了,但是还有另一个问题,如果我想定义另一个Speaker,比如NamedSpeaker,那么我必须编写两次代码——一次为NamedSpeaker,一次为&NamedSpeaker。

用泛型Trait解决这个问题
我可以写一个泛型的实现:“如果一个struct T实现了Speaker,那么写一个用于&T的Speaker实现。”
/// 所有实现Speaker的事物的引用也必须是Speaker的。
impl<T> Speaker for &T
where
    T: Speaker,
{
    fn speak(&self) {
        return (**self).speak();
    }
}
或者,如果你喜欢,你可以使用下面的,稍微短一点的语法,意思是一样的:
impl<T: Speaker> Speaker for &T {
    fn speak(&self) {
        return (**self).speak();
    }
}
即使我现在已经为&BasicSpeaker编写了Speaker的实现,但这并不适用于&mut BasicSpeaker!所以这行不通:
// 获取一个对BasicSpeaker的可变引用
let speaker_mut_ref: &mut BasicSpeaker = &mut speaker;

// 通过可变引用,调用在BasicSpeaker上定义的speak方法
speak_to(speaker_mut_ref);
这需要另一个泛型实现:
/// 所有实现Speaker的事物的可变引用也必须是Speaker的。
impl<T> Speaker for &mut T
where
    T: Speaker,
{
    fn speak(&self) {
        return (**self).speak();
    }
}
为了完整起见,这里对于Box<T>也是一样的,当你想把Speaker实现放到堆上时:
impl<T> Speaker for Box<T>
where
    T: Speaker,
{
    fn speak(&self) {
        return (**self).speak();
    }
}
一旦添加了这些覆盖实现,就意味着Speaker的任何新类型(该类型本身、对该类型的任何引用以及包含该类型的任何Box)都自动实现了Speaker trait。

总结
因为Rust会自动解除对trait的引用,它看起来就像引用本身也实现了trait。但事实并非如此。幸运的是,在许多情况下,你可以使用一些泛型trait来修复这个问题。如果你的trait接口允许,你应该为&T, &mut T和Box<T>提供trait实现,这样你就可以将这些类型传递给任何接受trait实现的函数。
用户评论