liveBook · Manning

Newtype idiom

As explained in Write Powerful Rust Macros

A newtype is a distinct wrapper type around an existing value that gives the value stronger type-level meaning and can enforce construction invariants.

A newtype wraps an existing value in a distinct type so the compiler can distinguish values that share the same representation. Instead of accepting several interchangeable `String` and `i32` arguments, a pay-raise function can require `FirstName`, `LastName`, `Age`, and `Pay`. This prevents accidentally swapping a name with another name or pay with age, while making the API describe the domain directly. section 2.2.3

Listing 2.6 Making mistakes
fn calculate_raise(first_name: String,
                   _last_name: String,
                   _age: i32,
                   current_pay: i32) -> i32 {
    if first_name == "Sam" { #1
        current_pay + 1000
    } else {
        current_pay
    }
}                

fn main() {
    let first_raise = calculate_raise(
        "Smith".to_string(), #2
        "Sam".to_string(),
        20,
        1000
    );             
    println!("{}", first_raise);      #3

    let second_raise = calculate_raise(
        "Sam".to_string(),
        "Smith".to_string(),
        1000, #4
        20
    );            
    println!("{}", second_raise);       #5
}

Identically typed parameters allow calls that compile but put a last name in the first-name position and pay in the age position. section 2.2.3

In Rust, define a wrapper struct with a private inner value and expose a constructor plus focused accessors. A constructor can validate input before creating the value: `FirstName::new` rejects names shorter than two characters. Function signatures then demand the wrappers rather than their underlying representations. section 2.2.3

Listing 2.7 The FirstName newtype
struct FirstName {
    value: String,
}

impl FirstName {
    pub fn new(name: &str) -> Result<FirstName, String> {
        if name.len() < 2 {
            Err("Name should be at least two characters".to_string())
        } else {
            Ok(FirstName {
                value: name.to_string(),
            })
        }
    }

    pub fn get_value(&self) -> &String {
        &self.value
    }
}

// code for the other three newtypes

fn calculate_raise(first_name: FirstName, #1
                   _last_name: LastName,
                   _age: Age,
                   current_pay: Pay) -> Pay {
    // ...
}

`FirstName` keeps its `String` private, validates construction in `new`, and changes `calculate_raise` to accept domain-specific types. section 2.2.3

A type alias improves readability but does not create a new type. With `type FirstName = String`, callers may still pass any `String` where a first name is expected. Use an alias for a shorter spelling of a complex type or a shared `Result` error type; use a newtype when the type system must enforce the distinction. section 2.2.3

Newtypes are useful when values need domain meaning or controlled construction:

  • Separate same-representation fields such as names, ages, and pay in function parameters. section 2.2.3
  • Hide a raw value behind a public API that is easier to understand and evolve. section 2.2.3
  • Generate repeated accessors or conversion implementations with declarative macros when several wrappers share a shape. section 2.2.3 section 2.4

Reducing accessor boilerplate

Several wrappers may all store a `value` field but need accessors returning different underlying types. A `generate_get_value!` macro can accept a struct identifier and optional return type, using `String` as the default. Keep the macro focused on repeated implementation detail; the wrapper types and their validation rules remain explicit. section 2.2.3

How Write Powerful Rust Macros covers it

How deep it goes
Works through wrapper structs, private fields, validating constructors, accessors, and macro-generated repeated methods; defers conversion implementations to exercises.
Examples
Uses a pay-raise call with swapped strings and integers, then FirstName, LastName, Age, and Pay wrappers.
What it assumes
Assumes working knowledge of Rust structs, impl blocks, Result, and declarative macro syntax.
What you can do after
Build it from scratch by defining validated wrapper structs and reducing repeated accessor or conversion code with declarative macros.
How it connects
Connects newtypes with declarative macros, type aliases, domain-driven design, From, and TryFrom.

Also explained in

Compare how 4 books explain newtype idiom →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Write Powerful Rust Macros. The section links point to the parts of the book it draws on.