Rust Patterns: Using traits for function overloading

Rust doesn’t have function or method overloading built in, but you can use the extremely flexible trait system to get something very much like it.

Let’s imagine we are writing a simple HTTP server framework and we’d like to expose a method on our Response representation to set the body of the response. Since our Response stores the body as a Reader trait object, we can just write a simple method which accepts a Reader:

impl Response {
pub fn set_body(&mut self, reader: Box<Reader + Send>) {
self.body = reader;
}
}

That was easy — but, before we get ahead of ourselves, let’s try some typical use cases:

Reading from an arbitrary reader:

res.set_body(Box::new(get_reader()));

Reading from a file:

use std::io::File;

res.set_body(
Box::new(File::open(Path::new("./file.html")).unwrap())
);

Reading from a string literal, Vec<u8>, or String:

use std::io::MemReader;

res.set_body(
Box::new(MemReader::new("bytes".as_bytes().to_vec()))
);

It doesn’t take a lot of playing around to see that our API is incredibly verbose to use and unergonomic in several common cases — we can do better.


How can we solve this problem? We’d like to shield our users from the complexity of getting a Reader over some type, so we’ll just write a trait for converting to that type.

trait IntoReader {
type OutReader: Reader;

fn into_reader(self) -> Self::OutReader;
}

Then, we’ll change our Response method to be generic over this trait:

impl Response {
pub fn set_body<I>(&mut self, data: I)
where I: IntoReader, I::OutReader: Send {
self.body = Box::new(data.into_reader());
}
}

Now our set_body method will accept any of a multitude of types, all that’s left is to implement the IntoReader trait for the types we want to accept:

use std::io::MemReader;

impl IntoReader for String {
type OutReader = MemReader;

fn into_reader(self) -> MemReader {
MemReader::new(self.into_bytes())
}
}

impl IntoReader for Path {
type OutReader = File;

fn into_reader(self) -> File {
File::open(self).unwrap()
}
}

// Some more impls for Vec<u8>, &str, &[u8], Box<Reader> etc.

Now we can test our earlier use cases:

Reading from an arbitrary Reader:

res.set_body(reader);

Reading from a file:

res.set_body(Path::new("./file.html"));

Reading from a string literal, Vec<u8>, etc.:

res.set_body(data);

By using a trait and a little bit of hidden polymorphism, we’ve managed to turn our cruddy API into a lean, efficient, and very easy to use one. The result of this transformation is very similar to what you’d get from method overloading, but much more extensible since downstream users can implement IntoReader for their own types!


In Iron, we took this trick to its logical extreme and created rust-modifier for use with editing Responses. Modifiers allow setting the body much like the API we built above, but also allow users to set all sorts of other attributes and even define arbitrary new modifiers in downstream code.