programowanieProgramista Backend

Jaka jest różnica między używaniem move a borrow podczas przekazywania zmiennych do funkcji w Rust?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Podczas przekazywania zmiennej do funkcji, może być ona przekazana przez odniesienie (borrow, za pomocą & lub &mut), lub przeniesiona (move, bez odniesienia).

Borrow: przekazywane jest odniesienie do danych. Dane pozostają dostępne po wywołaniu funkcji, ale dla niezmiennego odniesienia nie można zmieniać zawartości, dla zmiennego — może być tylko 1 aktywne odniesienie.

fn read_length(s: &String) -> usize { s.len() }

Move: zmienna całkowicie "przeprowadza się" do funkcji. Po przekazaniu nie będziesz mógł używać oryginalnej zmiennej — została przeniesiona, a wszelkie próby dostępu spowodują błąd kompilacji.

fn destroy(s: String) { println!("{}", s); } // s zostanie zniszczona przy wyjściu let s = String::from("world"); destroy(s); // s już nie można używać

Zapobiega to podwójnemu zwolnieniu pamięci i innym błędom związanym z własnością.

Pytanie z pułapką

Czy mogę używać zmiennej po tym, jak została przekazana do funkcji przez wartość (move)?

Nie! Po przekazaniu zmiennej przez wartość — na przykład String — oryginalna zmienna staje się nieważna:

let s = String::from("abc"); consume(s); // s jest nieważna tutaj println!("{}", s); // błąd kompilacji

Wielu myli to z zachowaniem typów z Copy (na przykład i32), gdzie zmienna pozostaje ważna po przekazaniu.

Przykłady rzeczywistych błędów z powodu braku znajomości niuansów tematu


Historia

Młody programista napisał funkcję do przetwarzania łańcucha, która przyjmowała String, a nie &String. W rezultacie oryginalny łańcuch po wywołaniu funkcji stawał się niedostępny, co prowadziło do podwójnego załadowania i dodatkowego przydziału pamięci w serwerze logowania.


Historia

W jednym mikroserwisie krytyczny zasób był przekazywany do różnych wątków przez move, po czym próby uzyskania dostępu do tego zasobu w wątku głównym prowadziły do błędu kompilacji. Trzeba było pilnie refaktoryzować architekturę, aby zasób był przekazywany przez odniesienie lub przez opakowania typu Arc.


Historia

W wewnętrznym parserze zdarzeń podczas przetwarzania danych zmienna była szybko zabierana (move) do zamknięcia, po czym odwołania do niej poza closure powodowały błędy kompilacji. Problem zauważono dopiero podczas przeglądu — wprowadzono styl obowiązkowego użycia borrow dla danych, które powinny żyć dłużej niż lokalna funkcja.