Aliasing Traits in Rust

Justin Wernick <>
Curly Tail, Curly Braces
2017-01-15

Abstract

In my article on programming with generic types in Rust, I found that some of the type declarations got pretty long and repetitive. In this article, I show how you can alias a collection of traits together into one trait.

Sometimes traits get verbose

If you read my previous post on programming with generic types, you would have seen that you need to make heavy use of Rust's trait system when you do generic programming.

Towards the end, I had a code block that looked a bit like this:

use std::ops::{Add, Sub, Mul};

impl<T> Mul for Complex<T> where T: Add<Output=T> + Sub<Output=T> + Mul<Output=T> + Clone {
    //implementation for multiplication goes here
}

That's a lot of code to read just listing out the operators I need T to implement. I also found that it tended to repeat with small variants in many places in the code.

The <Output=T> on everything (indicating that if you add two Ts together, you get another T) is also tedious.

Let's make an alias

This is actually a trick. Rust doesn't have trait aliases, but with this technique you can get the same effect.

  1. Define a new trait, which is a combination of all the traits you want.

  2. Write a generic implementation for your new trait, where the generic type has to already implement all of the other traits.

The Rust compiler can figure out how to connect things together. I think it's easier to express this in terms of an example.

use std::ops::{Add, Sub, Mul, Div};

// 1. Create a new trait
trait ArithmeticOps: 
    Add<Output=Self> + Sub<Output=Self> + Mul<Output=Self> + Div<Output=Self> 
    where Self: std::marker::Sized {
    // we'd usually add more functions in this block,
    // but in this case we don't need any more.
}

// 2. Implement it
impl<T> ArithmeticOps for T 
    where T: Add<Output=T> + Sub<Output=T> + Mul<Output=T> + Div<Output=T> {
    // Nothing to implement, since T already supports the other traits.
    // It has the functions it needs already
}

And the change to the code that uses these functions is like so:

// before
impl<T> Complex<T> where T: Add<Output=T> + Sub<Output=T> + Mul<Output=T> + Div<Output=T> {
}

// after
impl<T> Complex<T> where T: ArithmeticOps {
}

That's all. I now have a trait called ArithmeticOps which I can use whenever I want my type to implement all of the arithmetic operators.

Where Self is Sized?

In the last example, I sneaked in where Self: std::marker::Sized. What is Sized? This is a trait that tells the Rust compiler that it needs to know the size that Self will take up in memory at compile time.

Why do we need this?

If you look at the definition of Add from the documentation, it looks like this:

pub trait Add<RHS = Self> {
    type Output;
    fn add(self, rhs: RHS) -> Self::Output;
}

You need a function that takes in two Self's by value and returns one Self (because <Output=Self>). To pass something by value, it needs to be Sized.

If I was passing a pointer to a T, then I wouldn't necessarily need the compiler to know the size of T, because the pointer itself has a size that's known at compile time.

How did you know it needed to be sized?

In short, because I did it wrong and the compiler told me. The error message when I did it wrong was pretty clear about how I could fix the problem as well.

the trait `std::marker::Sized` is not implemented for `Self`
help: consider adding a `where Self: std::marker::Sized` bound
note: required by `std::ops::Add`

Let's add one more

In the my post on generic types, I mentioned that we may want to handle signed numbers and unsigned numbers differently, because unsigned numbers will support negation. I added negation to my list of ops like so:

// 1. Create a new trait
trait SignedArithmeticOps: ArithmeticOps + Neg<Output=Self>
    where Self: std::marker::Sized {}

// 2. Implement it
impl<T> SignedArithmeticOps for T 
    where T: ArithmeticOps + Neg<Output=T> {}

If you'd like to share this article on social media, please use this link: https://www.worthe-it.co.za/blog/2017-01-15-aliasing-traits-in-rust.html

Tag: blog


Related Articles

What's in a Generic Number Type?

I've been working on a small signal processing library as a learning exercise. As part of doing this, I've been exploring using generic types in Rust. This article is my notes on that exploration.

What Note Is This? Part 1: What is sound, and how do I use it in a program?

I'm currently working on a project to help me to improve my trumpet playing by giving me real-time feedback on pitch. This is the first post in a two part series, where I discuss what sound is, and how you can get it into your computer.