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
- Wzorzec Builder
- Wzorzec Newtype
- Buildery ze stanem typu
- Deref i ergonomia wrapperów