0Pricing
Learn Rust Coding · Урок

Шаблон Builder

Создавайте объекты шаг за шагом.

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

Зачем нужен Builder

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

Шаблон Builder решает эту проблему. Вы настраиваете объект пошагово с помощью именованных методов, а затем вызываете итоговый build(), чтобы получить значение. Такой код читается как связное предложение.

Целевая структура

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

Обратите внимание: поля закрыты, чтобы поощрить создание через Builder, а не с помощью литералов структуры.

pub struct ServerConfig {
    host: String,
    port: u16,
    max_connections: usize,
    use_tls: bool,
}

Отдельный тип Builder

Классический подход использует вторую структуру — Builder. Она повторяет целевую структуру, но хранит состояние процесса создания. Необязательные поля часто становятся Option<T>, чтобы можно было отличить состояние «не задано» от состояния «задано явно».

pub struct ServerConfigBuilder {
    host: String,
    port: Option<u16>,
    max_connections: Option<usize>,
    use_tls: bool,
}

Начало работы с Builder

Создайте для Builder конструктор, принимающий только обязательные поля. Все необязательные поля изначально получают значение None или значение по умолчанию.

Распространённое соглашение — метод builder() у целевого типа, возвращающий Builder.

impl ServerConfig {
    pub fn builder(host: impl Into<String>) -> ServerConfigBuilder {
        ServerConfigBuilder {
            host: host.into(),
            port: None,
            max_connections: None,
            use_tls: false,
        }
    }
}

Методы-сеттеры принимают self по значению

Каждый сеттер потребляет self, изменяет поле и возвращает self. Возврат значения, которым вы владеете, позволяет удобно объединять вызовы в цепочку.

Такая цепочка на основе владения — идиоматичный стиль Rust, который также избавляет от сложностей со временем жизни.

impl ServerConfigBuilder {
    pub fn port(mut self, port: u16) -> Self {
        self.port = Some(port);
        self
    }
    pub fn use_tls(mut self, yes: bool) -> Self {
        self.use_tls = yes;
        self
    }
}

Применение значений по умолчанию в build()

Итоговый вызов build() превращает Builder в настоящий тип. Здесь для всех полей, которые всё ещё имеют значение None, подставляются значения по умолчанию с помощью unwrap_or.

impl ServerConfigBuilder {
    pub fn build(self) -> ServerConfig {
        ServerConfig {
            host: self.host,
            port: self.port.unwrap_or(8080),
            max_connections: self.max_connections.unwrap_or(128),
            use_tls: self.use_tls,
        }
    }
}

Последовательное создание

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

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

fn main() {
    let cfg = ServerConfig::builder("localhost")
        .port(9000)
        .use_tls(true)
        .build();
    println!("{}:{} tls={}", cfg.host, cfg.port, cfg.use_tls);
}

Полный запускаемый пример

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

struct Config { name: String, retries: u32 }
struct Builder { name: String, retries: Option<u32> }
impl Config {
    fn builder(name: &str) -> Builder {
        Builder { name: name.to_string(), retries: None }
    }
}
impl Builder {
    fn retries(mut self, n: u32) -> Self { self.retries = Some(n); self }
    fn build(self) -> Config {
        Config { name: self.name, retries: self.retries.unwrap_or(3) }
    }
}
fn main() {
    let c = Config::builder("job").build();
    println!("{} retries={}", c.name, c.retries);
}

Создание с обработкой ошибок через Result

Иногда конфигурация может быть недопустимой, например если порт равен нулю. Сделайте так, чтобы build() возвращал Result, тогда ошибки проверки будут проявляться как ошибки, которые можно обработать, а не как паники.

impl ServerConfigBuilder {
    pub fn try_build(self) -> Result<ServerConfig, String> {
        let port = self.port.unwrap_or(8080);
        if port == 0 {
            return Err("port must be non-zero".into());
        }
        Ok(ServerConfig { host: self.host, port,
            max_connections: self.max_connections.unwrap_or(128),
            use_tls: self.use_tls })
    }
}

Ящик derive_builder

Писать строители вручную — повторяющаяся работа. Ящик derive_builder генерирует весь строитель на основе аннотации.

Вы помечаете поля значениями по умолчанию и бесплатно получаете сгенерированный FooBuilder с методами-установщиками и build(), который может завершиться ошибкой.

use derive_builder::Builder;

#[derive(Builder)]
struct Channel {
    #[builder(default = "8080")]
    port: u16,
    name: String,
}

Строители, владеющие значением, и строители с изменяемой ссылкой

Существуют два стиля. Стиль с владением поглощает self и естественно поддерживает цепочки вызовов. Стиль с &mut self возвращает &mut Self и позволяет разбить построение на несколько операторов без повторного связывания переменной.

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

impl ServerConfigBuilder {
    pub fn port_ref(&mut self, port: u16) -> &mut Self {
        self.port = Some(port);
        self
    }
}

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

Проверьте, насколько хорошо Вы поняли стиль строителя с владеющим self.

Итоги

Шаблон строителя компенсирует отсутствие необязательных аргументов в Rust. Тип строителя хранит состояние незавершённой работы, методы-установщики поглощают и возвращают self для построения цепочек вызовов, а build() применяет значения по умолчанию и создаёт итоговое значение.

Используйте Result из build() для проверки данных, а чтобы избежать шаблонного кода, обратитесь к derive_builder.

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

Урок «Шаблон 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 структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

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

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

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

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

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

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