0Pricing
Learn Rust Coding · Урок

Builder с состоянием типа

Кодируйте допустимость в системе типов.

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

Проблема обычных строителей

Обычный строитель позволяет вызвать build() в любой момент, даже до заполнения обязательных полей. Отсутствующие данные в таком случае приводят к панике во время выполнения или к значению Err.

Строители с типовым состоянием переносят эту проверку на этап компиляции: если забыть обязательный шаг, программа просто не скомпилируется.

Кодирование состояния в типах

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

Метод build() становится доступен только после того, как каждый обязательный маркер перейдёт в состояние «установлен».

struct Missing;
struct Set;

Строитель, обобщённый по маркерам

Добавьте в тип строителя параметры состояния для каждого обязательного поля. PhantomData хранит маркер, не занимая место под реальные данные.

use std::marker::PhantomData;
struct ReqBuilder<H, U> {
    url: Option<String>,
    method: Option<String>,
    _state: PhantomData<(H, U)>,
}

Начальное состояние

Конструктор возвращает строитель, у которого каждый обязательный маркер имеет значение Missing. На этом этапе build() ещё не существует, поэтому система типов знает, что объект не завершён.

impl ReqBuilder<Missing, Missing> {
    fn new() -> Self {
        ReqBuilder { url: None, method: None, _state: PhantomData }
    }
}

Переход маркера

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

impl<U> ReqBuilder<Missing, U> {
    fn url(self, url: &str) -> ReqBuilder<Set, U> {
        ReqBuilder { url: Some(url.to_string()),
            method: self.method, _state: PhantomData }
    }
}

Второй переход

Установка метода работает так же: второй маркер меняется с Missing на Set, а первый остаётся без изменений.

impl<H> ReqBuilder<H, Missing> {
    fn method(self, m: &str) -> ReqBuilder<H, Set> {
        ReqBuilder { url: self.url,
            method: Some(m.to_string()), _state: PhantomData }
    }
}

build() только после полной установки

Важно, что build() реализован только для ReqBuilder<Set, Set>. В любом другом состоянии такого метода просто нет, поэтому вызов не пройдёт компиляцию.

Внутри вызовы unwrap никогда не вызовут панику, потому что тип доказывает наличие обоих полей.

struct Request { url: String, method: String }
impl ReqBuilder<Set, Set> {
    fn build(self) -> Request {
        Request { url: self.url.unwrap(), method: self.method.unwrap() }
    }
}

Собираем всё вместе

Корректная цепочка успешно компилируется, потому что каждый вызов приближает строитель к ReqBuilder<Set, Set>, где существует build().

fn demo() -> Request {
    ReqBuilder::new()
        .url("https://example.com")
        .method("GET")
        .build()
}

Именно та ошибка компиляции, которая нужна

Пропустите обязательный шаг — и компилятор откажется продолжать. Вызов build() для ReqBuilder<Set, Missing> сообщает «метод с именем build отсутствует», обнаруживая пропуск ещё до запуска программы.

// ReqBuilder::new().url("x").build();
// error: no method named `build` found for
// ReqBuilder<Set, Missing>

Самодостаточная рабочая версия

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

use std::marker::PhantomData;
struct Missing; struct Set;
struct B<N> { name: Option<String>, _s: PhantomData<N> }
impl B<Missing> {
    fn new() -> Self { B { name: None, _s: PhantomData } }
    fn name(self, n: &str) -> B<Set> {
        B { name: Some(n.to_string()), _s: PhantomData }
    }
}
impl B<Set> {
    fn build(self) -> String { self.name.unwrap() }
}
fn main() {
    let v = B::new().name("prod").build();
    println!("built: {}", v);
}

Затраты и компромиссы

Строители с типовым состоянием обеспечивают гарантии на этапе компиляции без затрат во время выполнения, поскольку маркеры удаляются. Цена этого подхода — более сложная система типов и комбинаторный рост блоков реализации по мере увеличения числа обязательных полей.

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

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

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

Итоги

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

Поскольку build() реализован только для состояния, где все маркеры имеют значение Set, пропущенный шаг приводит к ошибке компиляции, а не к панике во время выполнения, и всё это без затрат во время выполнения.

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

Урок «Builder с состоянием типа» бесплатный?

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

Чему я научусь в уроке «Builder с состоянием типа»?

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

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

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

Сколько времени занимает урок «Builder с состоянием типа»?

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

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

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

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

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