[Advanced Rust] 2.4. API Design Principles of Unsurprising Pt.4 - Ergonomic Trait Implementations, Wrapper Types, and Borrow Tr…
[Advanced Rust] 2.4. API Design Principles of Unsurprising Pt.4 - Ergonomic Trait Implementations, Wrapper Types, and Borrow Trait
2.4.1. Ergonomic Trait Implementations
Rust does not automatically provide implementations for references to a type that implements a given trait. For example, if Bar implements Trait, you still cannot pass &Bar to fn foo<T: Trait>(t: T). That is because implementing Trait for Bar does not automatically implement Trait for &Bar.
2.4.1. 符合人体工程学的 Trait 实现
Rust 不会自动为实现了某个 Trait 的类型的引用提供相应的实现。例如,如果 Bar 实现了 Trait,你仍然无法将 &Bar 传递给 fn foo<T: Trait>(t: T)。这是因为为 Bar 实现 Trait 并不会自动为 &Bar 实现 Trait。
Example:
trait Trait { fn name(&self) -> &'static str; }
struct Bar;
impl Trait for Bar { fn name(&self) -> &'static str { "Bar" } }
fn foo<T: Trait>(t: T) { println!("{}", t.name()); }
fn main() {
let bar = Bar;
foo(bar); // OK
let bar_ref = &Bar;
foo(bar_ref); // error[E0277]: the trait bound `&Bar: Trait` is not satisfied
}
If a user sees that a trait method only accepts &self (and not self or &mut self), they may still be surprised that &Bar does not satisfy T: Trait. That does not satisfy the unsurprising principle. To solve this, when defining a new trait, we usually provide corresponding blanket implementations for the following (when the trait methods allow it—typically methods that take &self or &mut self):
&TwhereT: Trait + ?Sized&mut TwhereT: Trait + ?SizedBox<T>whereT: Trait + ?Sized
如果用户看到某个 Trait 方法只接受 &self(而不是 self 或 &mut self),他们可能会对 &Bar 不满足 T: Trait 感到惊讶。这不符合“不令人惊讶原则”(Unsurprising Principle)。为了解决这个问题,在定义新 Trait 时,我们通常会为以下类型提供相应的全覆盖实现(Blanket Implementation,前提是 Trait 方法允许,通常指那些接收 &self 或 &mut self 的方法):
&T其中T: Trait + ?Sized&mut T其中T: Trait + ?SizedBox<T>其中T: Trait + ?Sized
Continuing the example above, to prevent foo(bar_ref); from failing, we need to manually provide a Trait implementation for &T:
impl<T: Trait + ?Sized> Trait for &T {
fn name(&self) -> &'static str { (**self).name() }
}
继续上面的例子,为了防止 foo(bar_ref); 报错,我们需要手动为 &T 提供 Trait 实现:
impl<T: Trait + ?Sized> Trait for &T {
fn name(&self) -> &'static str { (**self).name() }
}
Note: if a trait method takes self by value (consuming ownership), you generally cannot provide a blanket impl Trait for &T that forwards to T, because a shared reference cannot move out of T.
注意:如果 Trait 方法通过值接收 self(消耗所有权),通常无法提供转发给 T 的全覆盖实现 impl Trait for &T,因为共享引用无法从 T 中移出所有权。
For iterators, if a type can be iterated, then its references should also provide the corresponding trait implementations. In other words: for any iterable type, consider implementing IntoIterator for &MyType and &mut MyType. That way, we can use borrowed values directly in loops, which matches user expectations.
对于迭代器,如果一个类型可以被迭代,那么它的引用也应该提供相应的 Trait 实现。换句话说:对于任何可迭代类型,请考虑为 &MyType 和 &mut MyType 实现 IntoIterator。这样,我们就可以直接在循环中使用借用的值,这符合用户的预期。
2.4.2. Wrapper Types
Rust does not have inheritance in the traditional object-oriented sense, but Deref and AsRef provide something similar. For example, if you have a value of type T and it satisfies Deref<Target = U>, then you can directly call methods from U on a value of type T.
2.4.2. 包装类型 (Wrapper Types)
Rust 没有传统面向对象意义上的继承,但 Deref 和 AsRef 提供了类似的功能。例如,如果你有一个类型为 T 的值,且它满足 Deref<Target = U>,那么你就可以直接在 T 类型的值上调用 U 的方法。
If you provide a relatively transparent type such as Arc<T>, then implementing Deref lets your wrapper type automatically dereference to the inner type at the point of use, so its methods can be called directly. If accessing the inner type does not require any complicated or potentially inefficient logic, you should consider implementing AsRef, so users can easily use &WrapperType as &InnerType.
如果你提供了一个相对透明的类型(如 Arc<T>),那么实现 Deref 可以让你的包装类型在使用时自动解引用为内部类型,从而直接调用其方法。如果访问内部类型不需要任何复杂或可能低效的逻辑,你应该考虑实现 AsRef,这样用户就可以轻松地将 &WrapperType 当作 &InnerType 使用。
For most wrapper types, you should also implement From<InnerType> for the wrapper and From<Wrapper> for the inner type where possible (which also gives you Into for free), so users can easily add or remove the wrapper.
对于大多数包装类型,在可能的情况下,你还应该为包装器实现 From<InnerType>,并为内部类型实现 From<Wrapper>(这也会免费获得 Into 实现),以便用户可以轻松地添加或移除包装。