0Pricing
Learn Rust Coding · Lekcja

Buildery ze stanem typu

Zakoduje Pan/Pani poprawność w systemie typów.

Buildery ze stanem typu to bezpłatna lekcja Learn Rust Coding na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Learn Rust Coding, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Learn Rust Coding zawiera 4 lekcji w sumie.

Problem ze zwykłymi builderami

Zwykły builder pozwala wywołać build() w dowolnym momencie, nawet przed ustawieniem wymaganych pól. Brakujące dane ujawniają się wtedy jako panic w czasie działania programu albo jako Err.

Buildery wykorzystujące stan typów przenoszą to sprawdzenie do czasu kompilacji: pominięcie wymaganego kroku po prostu uniemożliwia kompilację.

Kodowanie stanu w typach

Sztuczka polega na uogólnieniu buildera względem typów znacznikowych reprezentujących wykonane kroki. Po ustawieniu każdego pola zmienia się typ buildera.

Metoda build() staje się dostępna dopiero wtedy, gdy każdy wymagany znacznik osiągnie stan „set”.

struct Missing;
struct Set;

Builder uogólniony względem znaczników

Należy dodać do typu buildera parametry typów reprezentujące stan każdego wymaganego pola. PhantomData przechowuje znacznik bez zapisywania jakichkolwiek rzeczywistych danych.

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

Stan początkowy

Konstruktor zwraca builder, w którym każdy wymagany znacznik ma wartość Missing. Na tym etapie nie istnieje metoda build(), więc system typów wie, że obiekt jest niekompletny.

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

Zmiana stanu znacznika

Setter zużywa stary builder i zwraca nowy, w którym znacznik tego pola zmienia się na Set. Drugi znacznik pozostaje zachowany, ponieważ jego parametr typu nadal jest ogólny.

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

Druga zmiana stanu

Ustawienie metody działa tak samo: drugi znacznik zmienia się z Missing na Set, a pierwszy pozostaje bez zmian.

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() tylko po pełnym ustawieniu

Co najważniejsze, build() jest zaimplementowane tylko dla ReqBuilder<Set, Set>. W każdym innym stanie taka metoda po prostu nie istnieje, więc wywołanie powoduje błąd kompilacji.

Wywołania unwrap wewnątrz tej metody nigdy nie mogą wywołać panic, ponieważ typ potwierdza obecność obu pól.

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

Połączenie wszystkiego

Poprawny łańcuch kompiluje się bez problemu, ponieważ każde wywołanie przybliża builder do ReqBuilder<Set, Set>, w którym istnieje metoda build().

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

Błąd kompilacji, którego oczekujemy

Jeśli pominą Państwo wymagany krok, kompilator odmówi kompilacji. Wywołanie build() dla ReqBuilder<Set, Missing> zgłasza komunikat „no method named build”, wykrywając pominięcie, zanim program w ogóle zostanie uruchomiony.

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

Samodzielna wersja do uruchomienia

Ten minimalny program wykorzystuje jedno wymagane pole, aby pokazać pełną zmianę stanu od początku do końca. Kompiluje się i wypisuje zbudowaną wartość.

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);
}

Koszty i kompromisy

Buildery wykorzystujące stan typów zapewniają gwarancje w czasie kompilacji bez narzutu w czasie działania programu, ponieważ znaczniki są usuwane. Ceną jest większa złożoność systemu typów oraz kombinatoryczny przyrost bloków impl wraz ze wzrostem liczby wymaganych pól.

Ten wzorzec warto stosować w API, w których niekompletny build musi być niemożliwy z samej konstrukcji.

Szybkie sprawdzenie

Proszę wskazać, co faktycznie wymusza kompletność w builderze wykorzystującym stan typów.

Podsumowanie

Buildery wykorzystujące stan typów kodują każdy wymagany krok jako parametr typu będący znacznikiem, przechowywany przez PhantomData. Settery zużywają builder i zwracają nowy typ, w którym jeden znacznik zmienia się na Set.

Ponieważ build() jest zaimplementowane tylko dla stanu, w którym wszystkie znaczniki mają wartość Set, pominięcie kroku powoduje błąd kompilacji, a nie panic w czasie działania programu — i to bez żadnego kosztu w czasie działania.

Często zadawane pytania

Czy lekcja „Buildery ze stanem typu” jest bezpłatna?

Tak — pełny tekst „Buildery ze stanem typu” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Learn Rust Coding, przejdź na CoddyKit PRO. Kurs Learn Rust Coding zawiera 4 lekcji w sumie.

Co nauczysz się w „Buildery ze stanem typu”?

Zakoduje Pan/Pani poprawność w systemie typów. Ćwiczysz Learn Rust Coding z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Learn Rust Coding?

Nie wymagamy żadnego doświadczenia. Learn Rust Coding w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Buildery ze stanem typu”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Learn Rust Coding?

Tak. Każda lekcja Learn Rust Coding zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Wzorzec Builder
  2. Wzorzec Newtype
  3. Buildery ze stanem typu
  4. Deref i ergonomia wrapperów
← Powrót do Learn Rust Coding