Historia pytania
Rust zapożycza koncepcję metod z języków zorientowanych obiektowo, ale realizuje je inaczej: zamiast zwykłego this lub self, metody wyraźnie przyjmują parametr self. Funkcje stowarzyszone powstały jako alternatywa dla metod statycznych w innych językach programowania — są związane z typem, ale nie z konkretną wartością.
Problem
Często myli się metody, funkcje stowarzyszone i funkcje wolne. Nie jest oczywiste, kiedy używać metody z self, a kiedy funkcji stowarzyszonej bez self. Istnieją pytania dotyczące widoczności, automatycznej dereferencji, przekazywania własności.
Rozwiązanie
Metody w Rust są deklarowane z pierwszym parametrem self/ &self/ &mut self wewnątrz bloku impl (zwykle dla struct lub enum). Są wywoływane na instancji: object.method(). Funkcje stowarzyszone (np. new, from) są także deklarowane wewnątrz impl, ale bez pierwszego parametru self i są wywoływane przez podwójny dwukropek: Type::function().
Przykład kodu:
struct Point { x: f64, y: f64, } impl Point { // Funkcja stowarzyszona (konstruktor) fn new(x: f64, y: f64) -> Self { Self { x, y } } // Metoda: wymaga self fn distance_from_origin(&self) -> f64 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } let p = Point::new(3.0, 4.0); printf!("{}", p.distance_from_origin()); // 5.0
Kluczowe cechy:
::Czy można wywoływać funkcje stowarzyszone przez instancję (przez kropkę)?
Tak, jest to możliwe (np. p.new(1.0, 2.0)), ale zdecydowanie niezalecane: wprowadza to w błąd, ponieważ funkcja stowarzyszona nie ma dostępu do bieżącego obiektu, a instancja jest przekazywana, ignorując. Lepiej używać składni Type::func().
Przykład kodu:
let p = Point::new(1.0, 2.0); let q = p.new(0.0, 0.0); // Działa, ale nie jest to najlepsza praktyka!
Czy metody mogą być asynchroniczne?
Tak. Metody mogą być deklarowane z użyciem słowa kluczowego async w taki sam sposób jak funkcje wolne:
impl Foo { async fn do_async(&self) { // ... } }
Czy w jednym bloku impl można deklarować zarówno metody, jak i funkcje stowarzyszone?
Tak — wszelkie kombinacje są dozwolone. Możliwe jest również zadeklarowanie wielu bloków impl dla jednego typu.
Nowicjusz zadeklarował funkcję new poza impl i próbował użyć jej jako konstruktora, a potem przypadkowo wywołał przez instancję: p.new(1.0, 2.0).
Zalety:
Działa szybko (kompilator pozwala).
Wady:
Kod jest nieczytelny, trudny do utrzymania, trudno używać metod z odpowiednią własnością self.
Wszystkie metody i funkcje stowarzyszone są ściśle zadeklarowane wewnątrz impl, używane są poprawne składnie wywołania (Type::new() dla konstruktorów, obj.method() dla akcji).
Zalety:
Wysoka czytelność, zgodność z najlepszymi praktykami.
Wady:
Wymaga znajomości idiomów Rust i uwagi na składnię.