How do you impl Display for Vec?
Or, more generally, how do you implement any trait that is outside of your crate, for a type that is also outside of your crate?
Lets create a micro app that helps us explore the problem. We’ll create a simple struct, implement Display for that, then try to implement Display for a Vec of that struct.
Once we understand the problem we’ll discuss a simple solution and how to make that solution more idiomatic.
This post was originally published on GitHub where you can find working code examples.
Recreating the problem:
Lets start by making a small app to demonstrate the problem. The app is going to print out a list of music albums belonging to a user. Lets start with a simple representation of a music album.
We want our app to format the albums like this:
title (artist). So, if we write:
We want it to print:
Sgt. Pepper's Lonely Hearts Club Band (The Beatles)
To do this, we implement the
std::fmt::Display trait for Album to format the output
When we run the program, we get the expected result:
Now to tackle the problem at hand. Lets try to implement
fmt::Display for a
Albums. Lets start by creating the Vec and handing it to
Obviously this won’t work because we haven’t described how the Vec should be displayed, but lets do some “Compiler Driven Development” and get an idea of what to do next
As usual the compiler does it’s best to tell us whats wrong, and as we expected, it won’t compile until we implement
the trait std::fmt::Display is not implemented for std::vec::Vec<Album>
So let’s try that:
This function iterates over the vector writing each line to the formatter. We use
fold to combine all of the
fmt::Results we get from
writeln! so that we can return a single
fmt::Result from the function.
What happens when we try to compile now?
We have a new error, but if we look closely the compiler has told us why this doesn’t work.
note: the impl does not reference only types defined in this crate
In Rust you may implement traits from your crate onto types from other crates, or you may implent traits from other crates onto your types.
You can not apply external traits onto external types.
Vec are both in the standard library, neither is in our crate, we may not implement one for the other.
But, we can get around that.
The newtype pattern:
The solution to our problem is actually mentioned in the next line of the error:
note: define and implement a trait or new type instead
We can’t define a new trait since we need to use
Display, but we can define a new type. Obviously we don't want to create our own
Vec, but we can instead use the newtype pattern.
This pattern simply wraps one type in another. In our case we wrap the
Vec<Album>in our newtype
This pattern is normally used to improve type safety. For example if your program needs to deal with emails which are stored in Strings, you may want a function that only handles Emails and not any old String. You could use newtype idiom to enforce this:
struct Email(pub String)
We change our implementation of
fmt::Display to use our newtype, note that in addition to changing the “for”, we also have to use
self.0 to access the internal
We also need to wrap our
Vec in our
Albums newtype in our app code before we can print it.
Now our program outputs exactly what we wanted.
Sgt. Pepper's Lonely Hearts Club Band (The Beatles)
Dark Side of the Moon (Pink Floyd)
There’s still a problem though, lets dig a little further.
Remember we wanted to get the albums from a user. What type should we use here?
The obvious choice, and usually the right one, is to use
Albums, but that might not work in every use case.
If we’re only using the newtype for
Display it will add some mental overhead where we want to use the Vec underneath, so lets give our
Vec<Album> and look at how we can get our
Albums newtype for displaying it.
Uh oh! The problem here is that
User owns the
Vec<Album> data that we need for our newtype, to get at it we only have two options:
- We consume User and return just its albums
- We make a copy of the data in albums
Neither of these are particularly desirable, is there a better way?
Improving the solution
What if, rather than taking ownership of the data, the newtype just took a reference to the data? We can do that, it’s going to get a little rocky but it’ll be worth it:
“Argh! Lifetimes!” I hear you cry.
Don’t worry! This just tells the compiler that any use of
Albums must be tied to the same data that our reference of
Vec<Album> is tied to. If the
Vec<Album> is dropped, the compiler will tell us we can’t use
This does change our implementation a little because we now need to acknowledge the lifetime, but it’s not involved in the display itself so only the first line has to change.
We can now wrap the album data without having to take ownership of it. Here’s the new implementation of our user:
Where did the lifetimes go? Not that long ago, you would have had to specify the lifetimes on this function too, but today Rust is smart enough to know if one reference is going in (
&self), and one is coming out (
&self.albums) they must have the same lifetime.
Our code now works as you’d expect without making any unnecessary memory allocations, or consuming data we may want to use later.
There is one more little trick you can use to make your code even cleaner. Let’s go back to when we said the
User.albums probably should be the
Albums newtype, is there anything we can do to make using it easier?
For example, we don’t want to type
daniel.albums.0 every time we want access to the underlying vector. We shouldn't have to understand the implementation of the object in order to use it.
Well, there’s a trait for that,
std::ops::Deref. Going back to our original type that owned its data:
We can implement the
Deref trait to allow the outer type to be treated as though it is a reference to the inner type. We implement
Deref for Album like this:
We can take immediate advantage of this in our Display implementation:
Notice, we no longer need the
.0 when getting an iterator. The
iter function only needs a reference to the vector, it does not need ownership of it, so this works well.
Our User object now needs the newtype wrapper to go back in, but we can now treat albums as both a Albums type and a
You can remove the
impl User code entirely.
Here’s what we’ve learned:
- You can not apply external traits onto external types
- You can use the newtype idiom to wrap types, making it yours, allowing you to apply the external traits
- You can use use references inside of newtypes
- Or you can use the Deref trait to expose the contents of the newtype
Originally published at http://github.com.