Newtype

There's one thing I want to get out of the way near the start of the book and it didn't really have a good fit anywhere else in the book so it goes right here.

We're talking about the "Newtype Pattern"!

Now, I told you to read the Rust Book before you read this book, and I'm sure you're all good students who wouldn't sneak into this book without doing the required reading, so I'm sure you all remember exactly what I'm talking about, because they touch on the newtype concept in the book twice, in two very long named sections:

...Yeah... The Rust Book doesn't know how to make a short sub-section name to save its life. Shame.

Newtype Basics

So, we have all these pieces of data, and we want to keep them separated, and we don't wanna pay the cost for it at runtime. Well, we're in luck, we can pay the cost at compile time.


# #![allow(unused_variables)]
#fn main() {
pub struct PixelColor(u16);
#}

Ah, except that, as I'm sure you remember from The Rustonomicon (and from the RFC too, of course), if we have a single field struct that's sometimes different from having just the bare value, so we should be using #[repr(transparent)] with our newtypes.


# #![allow(unused_variables)]
#fn main() {
#[repr(transparent)]
pub struct PixelColor(u16);
#}

Ah, and of course we'll need to make it so you can unwrap the value:


# #![allow(unused_variables)]
#fn main() {
#[repr(transparent)]
pub struct PixelColor(u16);

impl From<PixelColor> for u16 {
  fn from(color: PixelColor) -> u16 {
    color.0
  }
}
#}

And then we'll need to do that same thing for every other newtype we want.

Except there's only two tiny parts that actually differ between newtype declarations: the new name and the base type. All the rest is just the same rote code over and over. Generating piles and piles of boilerplate code? Sounds like a job for a macro to me!

Making It A Macro

The most basic version of the macro we want goes like this:


# #![allow(unused_variables)]
#fn main() {
#[macro_export]
macro_rules! newtype {
  ($new_name:ident, $old_name:ident) => {
    #[repr(transparent)]
    pub struct $new_name($old_name);
  };
}
#}

Except we also want to be able to add attributes (which includes doc comments), so we upgrade our macro a bit:


# #![allow(unused_variables)]
#fn main() {
#[macro_export]
macro_rules! newtype {
  ($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
    $(#[$attr])*
    #[repr(transparent)]
    pub struct $new_name($old_name);
  };
}
#}

And we want to automatically add the ability to turn the wrapper type back into the wrapped type.


# #![allow(unused_variables)]
#fn main() {
#[macro_export]
macro_rules! newtype {
  ($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
    $(#[$attr])*
    #[repr(transparent)]
    pub struct $new_name($old_name);
    
    impl From<$new_name> for $old_name {
      fn from(x: $new_name) -> $old_name {
        x.0
      }
    }
  };
}
#}

That seems like enough for all of our examples, so we'll stop there. We could add more things, such as making the From impl optional (because what if you shouldn't unwrap it for some weird reason?), allowing for more precise visibility controls (on both the newtype overall and the inner field), and maybe even other things I can't think of right now. We won't really need those in our example code for this book, so it's probably nicer to just keep the macro simpler and quit while we're ahead.

As a reminder: remember that macros have to appear before they're invoked in your source, so the newtype macro will always have to be at the very top of your file, or in a module that's declared before other modules and code.