W Rust nie ma tradycyjnych konstruktorów jak w C++ czy Java, ale do tworzenia obiektów typów zazwyczaj używa się funkcji stowarzyszonych (często nazywanych new) oraz tzw. metod fabrycznych. To związane jest z historią języka, w którym szczególną uwagę przykłada się do bezpieczeństwa i jawności inicjalizacji: tylko jawnie napisana i wywołana funkcja odpowiada za poprawną inicjalizację każdego pola struktury.
Historia pytania
Początkowo w Rust inicjalizacja struktur dopuszczała bezpośrednie przypisanie wszystkich pól (tzw. składnia "struct literal"). Jednak aby zapewnić niezmienność, ukryć szczegóły i wdrożyć dodatkowe kontrole, praktykuje się użycie metod fabrycznych (impl SomeStruct { fn new(...) -> Self { ... } }) lub nawet generalizację przez wzorce (wzorzec budowniczego).
Problem
Główne zadania to nie dopuszczać do częściowo zainicjalizowanych obiektów i uniemożliwić użycie struktur w nieprawidłowym stanie. Jest to szczególnie krytyczne dla złożonych struktur (np. związanych z zasobami — plikami, gniazdami itd.), gdzie ręczna inicjalizacja wszystkich pól rodzi ryzyko błędów.
Rozwiązanie
W Rust zaleca się tworzenie metod fabrycznych, które zwracają w pełni zainicjalizowany obiekt, wykonują walidację w razie potrzeby i ukrywają szczegóły instancjonowania.
Przykład kodu:
struct User { username: String, age: u8, } impl User { pub fn new(username: String, age: u8) -> Option<Self> { if age >= 18 { Some(Self { username, age }) } else { None } } } fn main() { let user = User::new("Alice".to_string(), 20); // user: Option<User>, bezpiecznie obsłużyć błąd }
Kluczowe cechy:
fn new).Czy można zrobić prywatne pola w strukturze, aby nie można było tworzyć instancji bezpośrednio poza modułem?
Tak, jeśli wszystkie pola struktury uczynimy prywatnymi i udostępnimy tylko publiczne metody fabryczne, struktura nie może być inicjalizowana bezpośrednio poza swoim modułem.
Czy metoda fabryczna zawsze musi nazywać się new?
Nie, to umowa, ale nie obowiązek. Dla różnych strategii inicjalizacji używa się nazw takich jak "with_capacity", "from_config", "from_env" i tak dalej.
Czy mogą być prywatne konstruktory?
Tak, jeśli funkcja stowarzyszona jest zadeklarowana jako fn new(...) -> Self bez modyfikatora pub, nie będzie można jej wywołać poza tym modułem. Umożliwia to np. implementację singletona, wymuszenie fabryki lub ukrytą inicjalizację.
Bezpośrednie użycie struktury z otwartymi polami, bez metody fabrycznej:
struct Connection { fd: i32, timeout: u64, } let c = Connection { fd: -1, timeout: 10 }; // fd: -1 nie jest prawidłowy dla deskryptora!
Plusy:
Minusy:
Użycie prywatnych pól i metody fabrycznej:
pub struct Connection { fd: i32, timeout: u64, } impl Connection { pub fn new(fd: i32, timeout: u64) -> Option<Self> { if fd >= 0 { Some(Self { fd, timeout }) } else { None } } }
Plusy:
Minusy: