Newtype
What if in some cases we want a type to behave similar to another type or enforce some behaviour at compile time when using only type aliases would not be enough?
For example, if we want to create a custom Display
implementation for String
due to security considerations (e.g. passwords).
For such cases we could use the Newtype
pattern to provide type safety and
encapsulation.
Description
Use a tuple struct with a single field to make an opaque wrapper for a type.
This creates a new type, rather than an alias to a type (type
items).
Example
use std::fmt::Display; // Create Newtype Password to override the Display trait for String struct Password(String); impl Display for Password { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "****************") } } fn main() { let unsecured_password: String = "ThisIsMyPassword".to_string(); let secured_password: Password = Password(unsecured_password.clone()); println!("unsecured_password: {unsecured_password}"); println!("secured_password: {secured_password}"); }
unsecured_password: ThisIsMyPassword
secured_password: ****************
Motivation
The primary motivation for newtypes is abstraction. It allows you to share implementation details between types while precisely controlling the interface. By using a newtype rather than exposing the implementation type as part of an API, it allows you to change implementation backwards compatibly.
Newtypes can be used for distinguishing units, e.g., wrapping f64
to give
distinguishable Miles
and Kilometres
.
Advantages
The wrapped and wrapper types are not type compatible (as opposed to using
type
), so users of the newtype will never ‘confuse’ the wrapped and wrapper
types.
Newtypes are a zero-cost abstraction - there is no runtime overhead.
The privacy system ensures that users cannot access the wrapped type (if the field is private, which it is by default).
Disadvantages
The downside of newtypes (especially compared with type aliases), is that there is no special language support. This means there can be a lot of boilerplate. You need a ‘pass through’ method for every method you want to expose on the wrapped type, and an impl for every trait you want to also be implemented for the wrapper type.
Discussion
Newtypes are very common in Rust code. Abstraction or representing units are the most common uses, but they can be used for other reasons:
- restricting functionality (reduce the functions exposed or traits implemented),
- making a type with copy semantics have move semantics,
- abstraction by providing a more concrete type and thus hiding internal types, e.g.,
pub struct Foo(Bar<T1, T2>);
Here, Bar
might be some public, generic type and T1
and T2
are some
internal types. Users of our module shouldn’t know that we implement Foo
by
using a Bar
, but what we’re really hiding here is the types T1
and T2
, and
how they are used with Bar
.
See also
- Advanced Types in the book
- Newtypes in Haskell
- Type aliases
- derive_more, a crate for deriving many builtin traits on newtypes.
- The Newtype Pattern In Rust