0Pricing
Learn Rust Coding · Урок

Шаблон Newtype

Оборачивайте типы ради безопасности и ясности.

«Шаблон Newtype» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.

Что такое новый тип?

Новый тип — это кортежная структура с одним полем, которая оборачивает существующий тип и придаёт ему отдельную сущность. struct Meters(f64) — совершенно новый тип, хотя он и хранит обычное значение f64.

Такая оболочка не требует затрат во время выполнения, но позволяет компилятору обеспечивать смысловые ограничения, которых нельзя добиться с помощью необёрнутого примитивного типа.

struct Meters(f64);
struct Seconds(f64);

Предотвращение путаницы между единицами измерения

Необёрнутые примитивные типы легко перепутать. Если и расстояние, и время имеют тип f64, ничто не помешает Вам поменять их местами при вызове.

Если обернуть каждое значение в собственный новый тип, такая ошибка станет ошибкой компиляции, а не незаметной ошибкой в программе.

fn speed(d: Meters, t: Seconds) -> f64 {
    d.0 / t.0
}
// speed(Seconds(2.0), Meters(10.0)) -> compile error

Доступ к внутреннему значению

Получить обёрнутое значение можно через индекс кортежа .0. Многие новые типы также предоставляют метод или реализуют From для удобного преобразования.

struct UserId(u64);
impl UserId {
    fn value(&self) -> u64 { self.0 }
}
fn main() {
    let id = UserId(42);
    println!("id = {}", id.value());
}

Инкапсуляция инвариантов

Сделайте внутреннее поле закрытым и выполняйте проверку в конструкторе. Тогда любое значение нового типа будет гарантированно корректным, и последующему коду не придётся проверять его повторно.

pub struct Email(String);
impl Email {
    pub fn new(s: &str) -> Option<Email> {
        if s.contains('@') {
            Some(Email(s.to_string()))
        } else {
            None
        }
    }
}

Правило сирот

Rust запрещает реализовывать внешний трейт для внешнего типа. Нельзя написать impl Display for Vec<T>, потому что Вам не принадлежит ни трейт, ни тип.

Это правило обеспечивает корректность согласованности трейтов между ящиками, но иногда мешает создавать полезные реализации.

Новые типы обходят правило сирот

Поскольку новый тип определён в Вашем ящике, теперь это локальный тип, и Вы можете реализовать для него любой трейт. Это стандартный способ обойти правило сирот.

use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

Рабочая оболочка с реализацией Display

Здесь оболочка из предыдущей сцены используется на практике. Нам принадлежит Wrapper, поэтому реализация Display для него разрешена, и программа выводит объединённый список.

use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}
fn main() {
    let w = Wrapper(vec!["a".into(), "b".into()]);
    println!("{}", w);
}

Ограничение поверхности API

Обёртка мощного типа позволяет предоставить только безопасное подмножество его возможностей. NonEmptyVec может скрыть изменяющие методы, из-за которых он мог бы стать пустым, и тем самым сохранить свой инвариант.

pub struct NonEmptyVec<T>(Vec<T>);
impl<T> NonEmptyVec<T> {
    pub fn new(first: T) -> Self {
        NonEmptyVec(vec![first])
    }
    pub fn first(&self) -> &T {
        &self.0[0]
    }
}

Абстракция без затрат

Новый тип с одним полем имеет такую же структуру памяти, как и обёрнутое значение. Компилятор устраняет оболочку, поэтому безопасность в данном случае действительно не требует затрат во время выполнения.

Добавление #[repr(transparent)] гарантирует идентичную структуру памяти, что важно для FFI.

#[repr(transparent)]
struct Celsius(f64);

Автоматическая реализация трейтов для новых типов

Новые типы часто автоматически реализуют стандартные трейты, чтобы в подходящих случаях они вели себя как внутренние значения. Автоматическая реализация сохраняет удобство использования в качестве ключей, при сравнении и при отладочном выводе.

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct ProductId(u32);
fn main() {
    let a = ProductId(7);
    let b = a.clone();
    println!("{:?} == {:?}: {}", a, b, a == b);
}

Новый тип и псевдоним типа

Не путайте новый тип с псевдонимом типа. type Meters = f64 — всего лишь другое имя: это по-прежнему f64, и дополнительной безопасности оно не даёт.

Новый тип struct Meters(f64) действительно является отдельным типом, который компилятор может отличать от других.

type Km = f64;       // alias: interchangeable with f64
struct Mi(f64);      // newtype: distinct from f64

Быстрая проверка

Определите, почему новый тип помогает там, где псевдоним типа не помогает.

Итоги

Шаблон нового типа оборачивает существующий тип в кортежную структуру с одним полем, чтобы получить отдельную сущность без затрат во время выполнения. Он предотвращает путаницу между значениями, инкапсулирует инварианты за закрытым полем и обходит правило сирот, позволяя реализовывать внешние трейты.

В отличие от псевдонима типа, новый тип является настоящим отдельным типом.

Часто задаваемые вопросы

Урок «Шаблон Newtype» бесплатный?

Да — полный текст урока «Шаблон Newtype» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.

Чему я научусь в уроке «Шаблон Newtype»?

Оборачивайте типы ради безопасности и ясности. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Learn Rust Coding?

Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Шаблон Newtype»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Learn Rust Coding?

Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Шаблон Builder
  2. Шаблон Newtype
  3. Builder с состоянием типа
  4. Удобство Deref и обёрток
← Назад к Learn Rust Coding