How do you impl Display for Vec?

Daniel Mason
Jun 10 · 5 min read

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.


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 Vec of Albums. Lets start by creating the Vec and handing it to println!

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 Display for Vec

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.

Since fmt::Display and 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 Albums.

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 Vec.

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 User a 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:

  1. We consume User and return just its albums
  2. 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 Albums anymore.

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 &Vec<Album> type.

You can remove the impl User code entirely.

Conclusion

Here’s what we’ve learned:

  1. You can not apply external traits onto external types
  2. You can use the newtype idiom to wrap types, making it yours, allowing you to apply the external traits
  3. You can use use references inside of newtypes
  4. Or you can use the Deref trait to expose the contents of the newtype

Originally published at http://github.com.

Apolitical Engineering

Technical thoughts from the Apolitical engineering team

Daniel Mason

Written by

Apolitical Engineering

Technical thoughts from the Apolitical engineering team