Rust: Extending a Type

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:1

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:

  1. We bound T to Copy in order to keep the implementations simple.