programowanieRust Backend Developer

Jak działają metody i funkcje stowarzyszone w Rust? Czym się od siebie różnią, jak je deklarować i wywoływać oraz kiedy należy używać której wersji?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

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:

  • Metody przyjmują parametr self w różnych wariantach własności
  • Funkcje stowarzyszone nie przyjmują self, zwykle są używane do inicjalizacji lub jako narzędzia
  • Tylko metody mogą być wywoływane na instancji przez kropkę, funkcje stowarzyszone tylko przez ::

Pytania pułapki.

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.

Typowe błędy i antywzorce

  • Mylenie metod i funkcji stowarzyszonych
  • Pozostawianie funkcji stowarzyszonych bez wskazania przynależności do typu (nie deklarować w impl)
  • Wywoływanie funkcji stowarzyszonych przez instancję (przez kropkę)

Przykład z życia

Negatywny przypadek

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.

Pozytywny przypadek

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ę.