Extending Other Traits
As with types, it may be desirable to extend foreign traits. In particular, to attach new methods to all implementors of a given trait.
#![allow(unused)] fn main() { mod ext { use std::fmt::Display; pub trait DisplayExt { fn quoted(&self) -> String; } impl<T: Display> DisplayExt for T { fn quoted(&self) -> String { format!("'{}'", self) } } } pub use ext::DisplayExt as _; assert_eq!("dad".quoted(), "'dad'"); assert_eq!(4.quoted(), "'4'"); assert_eq!(true.quoted(), "'true'"); }
-
Highlight how we added new behavior to multiple types at once.
.quoted()can be called on string slices, numbers, and booleans since they all implement theDisplaytrait.This flavor of the extension trait pattern uses blanket implementations.
A blanket implementation implements a trait for all types
Tthat satisfy the trait bounds specified in theimplblock. In this case, the only requirement is thatTimplements theDisplaytrait. -
Draw the students’ attention to the implementation of
DisplayExt::quoted: we can’t make any assumptions aboutTother than that it implementsDisplay. All our logic must either use methods fromDisplayor functions/macros that don’t require other traits.For example, we can call
format!withT, but can’t call.to_uppercase()because it is not necessarily aString.We could introduce additional trait bounds on
T, but it would restrict the set of types that can leverage the extension trait. -
Conventionally, the extension trait is named after the trait it extends, followed by the
Extsuffix. In the example above,DisplayExt. -
There are entire crates that extend standard library traits with new functionality.
-
itertoolscrate provides theItertoolstrait that extendsIterator. It adds many iterator adapters, such asinterleaveandunique. It provides new algorithmic building blocks for iterator pipelines built with method chaining. -
futurescrate provides theFutureExttrait, which extends theFuturetrait with new combinators and helper methods.
-
More To Explore
-
Extension traits can be used by libraries to distinguish between stable and experimental methods.
Stable methods are part of the trait definition.
Experimental methods are provided via an extension trait defined in a different library, with a less restrictive stability policy. Some utility methods are then “promoted” to the core trait definition once they have been proven useful and their design has been refined.
-
Extension traits can be used to split a dyn-incompatible trait in two:
- A dyn-compatible core, restricted to the methods that satisfy dyn-compatibility requirements.
- An extension trait, containing the remaining methods that are not dyn-compatible (e.g., methods with a generic parameter).
-
Concrete types that implement the core trait will be able to invoke all methods, thanks to the blanket impl for the extension trait. Trait objects (
dyn CoreTrait) will be able to invoke all methods on the core trait as well as those on the extension trait that don’t requireSelf: Sized.