Newtype

Суть

Придание дополнительного смысла типу переменной. Например, строка String может быть использована для чего угодно, а нам нужно конкретизировать:

// Обычный String
let name = String::from("john");

// Newtypes - каждый тип уникален
struct Username(String);
struct Email(String);
struct Password(String);

let user = Username(String::from("john"));  // Не перепутать типы
let email = Email(String::from("john@example.com"));  // Они разные!

У данного паттерна НЕТ стоимости в скорости или ресурсах приложения.

Применение

Защита от смешивания типов:

struct Meters(f32);
struct Kilometers(f32);

fn race_distance(distance: Meters) {
    println!("Race is {} meters!", distance.0);
}

let m = Meters(100.0);
let km = Kilometers(0.1);

race_distance(m);     // ✅ Работает!
race_distance(km);    // ❌ ОШИБКА! Нельзя передать КМ туда, где ждут М

Дополнительные методы:

struct Age(u8);

impl Age {
    fn is_adult(&self) -> bool {
        self.0 >= 18
    }
    
    fn can_retire(&self) -> bool {
        self.0 >= 65
    }
}

let my_age = Age(16);
println!("Adult? {}", my_age.is_adult());  // false

Спрятать детали реализации:

pub struct CreditCardNumber(String);  // клиенты видят этот метод

impl CreditCardNumber {
    // Отдаём только безопасные методы
    pub fn last_four(&self) -> String {
        self.0[self.0.len()-4..].to_string()
    }
    
    // Внутренняя валидация происходит тут:
    pub fn new(number: String) -> Result<Self, String> {
        if number.len() == 16 {
            Ok(CreditCardNumber(number))
        } else {
            Err("Must be 16 digits".to_string())
        }
    }
}

// Нельзя извне обратиться к сырой строке!
let card = CreditCardNumber::new("1234567890123456".to_string()).unwrap();
println!("{}", card.last_four());  // "3456" ✅
// println!("{}", card.0);  ❌ ОШИБКА! Приватное поле!

Когда использовать паттерн Newtype

  • Когда есть два значения одного типа, но означают разные вещи (IDs, измерения);
  • Когда надо добавить методы к типу, которым мы не владеем (например, обернуть u32, чтобы добавить is_even());
  • Когда надо применить правила валидации.

Когда НЕ использовать паттерн Newtype

  • Когда действительно нужны все методы исходного типа (вместо этого используйте псевдоним типа: type Age = u8).