Sometimes it is desirable
to add extra methods
to a foreign type,
i.e. a type defined
in an external crate.
In Rust,
this can be accomplished
with traits.
Problem
As an example,
let’s add before
and after
methods
to the std::ops::Range<T>
type.
They should work like this:
(3..5).before(); // ..3
(3..5).after(); // 5..
A naive attempt
to add these methods
would look like this:
use std::ops::{Range, RangeFrom, RangeTo};
impl<T: Copy> Range<T> {
fn before(&self) -> RangeTo<T> {
..self.start
}
fn after(&self) -> RangeFrom<T> {
self.end..
}
}
But since Range<T>
is not a local type,
we can’t directly write an impl
block for it.
The compiler tells us this:
error: cannot define inherent `impl` for a type outside of the crate where the type is defined; define and implement a trait or new type instead [--explain E0116]
--> src/lib.rs:3:1
|>
3 |> impl<T: Copy> Range<T> {
|> ^
Solution
The first solution it suggests
is to define and implement a trait.
By convention,
such “extension traits”
are named ending in Ext
.
use std::ops::{Range, RangeFrom, RangeTo};
pub trait RangeExt<T> {
fn before(&self) -> RangeTo<T>;
fn after(&self) -> RangeFrom<T>;
}
We can then implement the trait
for the desired type,
since implementing local traits
for foreign types is allowed.
impl<T: Copy> RangeExt<T> for Range<T> {
fn before(&self) -> RangeTo<T> {
..self.start
}
fn after(&self) -> RangeFrom<T> {
self.end..
}
}
The two methods are now available
on Range<T>
objects
just like any other method.
To use the methods
in other modules,
it is necessary to use
the RangeExt
trait.
#[cfg(test)]
mod tests {
use super::RangeExt;
#[test]
fn range_ext() {
assert_eq!(..3, (3..5).before());
assert_eq!(5.., (3..5).after());
}
}
The example code is available in Gist form.
More examples of this pattern
can be found
in the standard library: