programowanieProgramista Rust

Jak działają zamknięcia (closures) w Rust? Jakie istnieją typy zamknięć, czym się różnią i kiedy są używane?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W Rust zamknięcia to anonimowe funkcje, które mogą "przechwytywać" zmienne z zewnętrznego zakresu. Składnia:

let add = |a: i32, b: i32| a + b;

W Rust istnieją trzy rodzaje zamknięć (różnią się sposobem przechwytywania zmiennych):

  • Fn: przechwytuje odniesienia (&T), można wywoływać wielokrotnie, nie mutując otoczenia.
  • FnMut: przechwytuje odniesienia z możliwością zmiany (&mut T), można zmieniać dane zamknięcia przy wywołaniu.
  • FnOnce: może być wywołane tylko raz, ponieważ przejmuje własność przechwyconych danych (T).

Rust automatycznie określa wymagany typ, ale można go wyraźnie wskazać.

Przykład różnic:

let s = String::from("hello"); // FnOnce let consume = move || println!("{}", s); // s nie jest już dostępne po wywołaniu

Pytanie z podchwytliwością

Dlaczego zamknięcie z kluczem move może nie być FnOnce, a być Fn lub FnMut?

Odpowiedź: Kluczowe słowo move przenosi przechwytywanie zmiennych przez własność, ale jeśli dane wewnątrz zamknięcia nie są mutowane i nie są usuwane, zamknięcie pozostaje zgodne z Fn/FnMut. Na przykład:

let s = String::from("abc"); let c = move || println!("String: {}", s); // c: impl Fn() c();

c można wywołać wielokrotnie: wartość s została skopiowana do zamknięcia, ale nie została zniszczona.

Przykłady rzeczywistych błędów z powodu nieznajomości szczegółów tematu


Historia

Początkujący programista Rust w projekcie z przepływem danych próbował napisać zamknięcie bez move, przekazując je do innego wątku. W rezultacie kompilator zgłaszał błąd outlived borrow, co wymuszało nagłe zgłębianie szczegółów lifetime i move.


Historia

Wprowadzono mutability do zamknięcia, nie zmieniając typu na FnMut, co powodowało "mismatched types" w każdym miejscu użycia iteratorów.


Historia

Podczas przetwarzania dużej tablicy zamknięcie przejmowało przez move całą kolekcję, nieświadomie przenosząc własność, przez co zewnętrzny kod tracił dostęp do danych po pierwszej iteracji, co prowadziło do prób uzyskania dostępu do moved value.