How to use type aliases in RUST

You just want to see code? -> Jump to the code examples.

What is a type alias?

I think about it as: using a synonym for a type. Or “this code is so freaking long I need a shorthand for it”.

Why is a type alias even useful?

  • It comes in handy for complex type signatures. Especially if this type signature is used in more than one place.
  • It can make your intend much clearer and therefore makes the code more readable.

When do those complex type signatures occur?

When you want to use more than basic/built-in types. Especially when generics and lifetimes have to be added aswell.

Unimportant background story:
In my case, I wanted to write a little parser that takes in some code and spits out xml. The pieces of code (tokens), that came into the parser, were categorized into something like: “Symbol”, “Keyword”, “Identifier”, yadda yadda yadda.
What I needed/wanted, was to pass this sequence of tokens around. So that different functions like “parse_if_statement” or “parse_while_loop” could be called with the same sequence object. To figure out which function to call, I wanted to look (or peek) what the next item was, without picking it of the sequence. This is what a Peekable allows you to do. It’s like a slightly advanced Iterator.
This might not sound too difficult. But boy, did that type signature get loooooong.

It took some help from stackoverflow to finally get a working type signature for my use case.
And the final function definition, with that elongated type signature, looked something like this:

fn parse_input<'a, I: Iterator<Item = &'a String>>(input: &mut Peekable<I>) -> String {...}

Shocking, right?! Not only is this quite some code and a half to type (imagine you also might have parse_input2 and pars_input3). Most of all it’s like: “What’s going ooooon?!”

And that’s where type aliases can help:

How to define an alias

  1. Declare an alias with the keyword “type” followed by the name of your type.
    (e.g. MyType, where the first letter is uppercase by convention)
  2. Assign a valid, existing type to it.
  3. Done.

Basic type alias example:

type Age = u32;

Now you can use it like this:

fn print_age(age: Age) {
  println!("{}", age);
}

print_age(42);

Awesome! The output is 42.

Though this is not really an advantage yet, you might already see the potential.
Let’s get a little bit more complex do demonstrate this.

Type alias for a vector

type MyVec = Vec<i32>;

fn iter_my_vec(some_vec: MyVec) {
  for num in some_vec.iter() {
    println!("{}", num);
  }
}

iter_my_vec(vec![1, 2, 3]);

This prints:
1
2
3

Ok. Now how about aliasing the Iterator type.

Type alias for Iterator (not finished)

!!!THIS DOES NOT COMPILE YET!!!

type MyIterator = std::slice::Iter<i32>;

fn print_items(iterable: MyIterator) {
  for num in iterable {
    println!("{}", num);
  }
}

print_items(vec![1, 2, 3].iter());

The above example is pretty close to what an iterator type alias could look like. But we need lifetime annotations here. (The details of lifetimes are beyond the scope of this post).

Type alias for an iterator (with lifetimes)

type MyIterator<'a> = std::slice::Iter<'a, i32>;

fn print_items(iterable: MyIterator) {
  for num in iterable {
    println!("{}", num);
  }
}

print_items(vec![1, 2, 3].iter());

This workes perfectly fine and again prints:
1
2
3

I would say, that the usage of MyIterator is quite a bit nicer than
std::slice::Iter<‘a, i32>.
But now we start to juice it up and alias the Peekable type, by using our fresh MyIterator inside of it.

Peekable over a type aliased Iterator

// Bringing Peekable into scope
use std::iter::Peekable;

type MyIterator<'a> = std::slice::Iter<'a, i32>;

fn print_items(peekable: &mut Peekable<MyIterator>) {
  while peekable.peek() != None {
      println!("{:?}", peekable.next());
  }
}

print_items(&mut vec![1, 2, 3].iter().peekable());

The output is going to be:
Some(1)
Some(2)
Some(3)

A brief explanation about what is going on. Skip ahead if you’ve already figured that out.

  • First we bring in the Peekable type into scope with use std::iter::Peekable.
  • Then, we alias the Iterator with Items of type i32 as MyIterator, like in the example before.
  • The print_items function now takes a Peekable that we define to “enclose” our MyIterator type. The type needs to be a mutable reference, because peek() and next() both take a mutable reference.
    This is denoted by adding &mut in front of the type.
  • In the body we have a while loop, that first checks, whether the next item is available (is not None) and if it is, prints it out.
    (This is not really useful in this case, but if you would like to call different functions, depending on the next item, you might first want to check what kind of item it is, without picking it off the iterator. This is exactly what the Peekable allows us to do.
  • Lastly we call the function print_items, by passing it a Vec with i32 items, on which we call iter() (make it iterable) and then call peekable(), which makes it a Peekable.

Pretty awesome!

To top it all off, we completely alias the Peekable away.

Type alias for Peekable (The Total Abstraction)

// Bringing Peekable into scope
use std::iter::Peekable;

type MyIterator<'a> = std::slice::Iter<'a, i32>;
type MyPeekable<'a> = Peekable<MyIterator<'a>>;

fn print_items(peekable: &mut MyPeekable) {
  while peekable.peek() != None {
      println!("{:?}", peekable.next());
  }
}

print_items(&mut vec![1, 2, 3].iter().peekable());

This is almost the same code as in the example before. Only lines 5 and 7 are new.

  • In line 5 we define our own MyPeekable type, which consists of the MyIterator type, defined earlier.
  • The print_items function on line 7 now takes our fresh type and looks so so so beautiful, don’t ya think?

And thats it! Look at what we’ve accomplished. We’ve turned a really verbose signature of:

fn print_items<'a>(peekable: &mut Peekable<std::slice::Iter<'a, i32>>) {...}

into something readable and pretty:

fn print_items(peekable: &mut MyPeekable) {...}

I don’t know how you feel, but this makes me really happy.

To wrap up

Type aliases are a fantastic feature in RUST to simplify complex type signatures. This is useful if you have to repeat that signature in several places. AND you can often express your intend a lot better, which makes the code much more understandable.
This post has only scratched the surface (I myself have just recently learned about this topic). But I hope I have peeked you interest (no pun intended).
Please feel free to send me an email or connect with me on twitter, if you have any comments, corrections or suggestions or if you liked this post.

sp├Ąters

niilz