# Ownership i Borrowing w Rust: Kompletny przewodnik po zarządzaniu pamięcią > Szczegółowy przewodnik po systemie ownership i borrowing w Rust. Semantyka przenoszenia, pożyczanie niemutowalne i mutowalne, czasy życia referencji oraz typowe wzorce stosowane w praktyce i na rozmowach kwalifikacyjnych. - Published: 2026-03-31 - Updated: 2026-03-31 - Author: Anthony Fillion-Maillet - Tags: rust, ownership, borrowing, memory-management, systems-programming - Reading time: 10 min --- System ownership stanowi fundament języka Rust i odróżnia go od wszystkich innych popularnych języków programowania. Dzięki niemu kompilator gwarantuje bezpieczeństwo pamięci bez garbage collectora, eliminując całe klasy błędów takich jak use-after-free, podwójne zwalnianie czy wyścigi danych. Zrozumienie ownership i borrowing jest kluczowe zarówno w codziennej pracy z Rustem, jak i podczas rozmów kwalifikacyjnych, gdzie te zagadnienia pojawiają się niemal zawsze. > **Trzy zasady ownership** > > System ownership w Rust opiera się na trzech regułach: (1) Każda wartość ma dokładnie jednego właściciela. (2) W danym momencie może istnieć tylko jeden właściciel. (3) Gdy właściciel wyjdzie z zakresu, wartość zostaje zwolniona. Te reguły są weryfikowane w czasie kompilacji -- bez narzutu w runtime. ## Semantyka przenoszenia (move semantics) W Rust przypisanie wartości do innej zmiennej domyślnie przenosi ownership. Po przeniesieniu oryginalna zmienna staje się niedostępna. Kompilator wykrywa każdą próbę użycia przeniesionej wartości i zgłasza błąd jeszcze przed uruchomieniem programu. ```rust // move_semantics.rs fn main() { let original = String::from("interview prep"); let moved = original; // ownership transfers here // println!("{}", original); // compile error: value moved println!("{}", moved); // works fine } ``` Przenoszenie dotyczy typów alokowanych na stercie, takich jak `String` czy `Vec`. Typy proste implementujące cechę `Copy` (np. `i32`, `f64`, `bool`) są kopiowane zamiast przenoszone. Dla typów nie-`Copy` można wykonać jawną głęboką kopię za pomocą metody `clone()`. ```rust // copy_vs_move.rs fn main() { let x: i32 = 42; let y = x; // copy, not move -- i32 is Copy println!("x = {}, y = {}", x, y); // both valid let s1 = String::from("hello"); let s2 = s1.clone(); // explicit deep copy println!("s1 = {}, s2 = {}", s1, s2); // both valid after clone } ``` Różnica między `Copy` a `Clone` jest istotna na rozmowach kwalifikacyjnych. `Copy` zachodzi niejawnie i dotyczy typów o stałym rozmiarze przechowywanych na stosie. `Clone` wymaga jawnego wywołania i może być kosztowne dla dużych struktur danych. ## Pożyczanie niemutowalne (immutable borrowing) Pożyczanie pozwala na dostęp do wartości bez przejmowania ownership. Referencja niemutowalna (`&T`) umożliwia odczyt danych bez ich modyfikowania. Może istnieć dowolna liczba jednoczesnych referencji niemutowalnych do tej samej wartości. ```rust // immutable_borrowing.rs fn calculate_length(s: &String) -> usize { s.len() // read access through the reference } // s goes out of scope, but doesn't drop the String (not the owner) fn main() { let greeting = String::from("hello, Rust"); let len = calculate_length(&greeting); // borrow, don't move println!("'{}' has {} characters", greeting, len); // greeting still valid } ``` Funkcja `calculate_length` przyjmuje referencję do `String`, nie sam `String`. Dzięki temu funkcja wywołująca zachowuje ownership, a wartość pozostaje dostępna po powrocie z funkcji. Ten wzorzec jest powszechnie stosowany w Rust -- przekazywanie referencji zamiast przenoszenia wartości. > **Reguły pożyczania** > > W Rust obowiązują dwie fundamentalne reguły pożyczania: (1) W danym momencie można mieć albo jedną mutowalną referencję, albo dowolną liczbę niemutowalnych referencji. (2) Referencje muszą być zawsze poprawne -- nie mogą wskazywać na zwolnioną pamięć. Borrow checker weryfikuje te reguły statycznie. ## Referencje mutowalne (mutable references) Referencja mutowalna (`&mut T`) pozwala na modyfikację pożyczonej wartości. W danym momencie może istnieć tylko jedna mutowalna referencja do konkretnej wartości. Ta zasada wyłączności zapobiega wyścigom danych na etapie kompilacji. ```rust // mutable_borrowing.rs fn append_greeting(s: &mut String) { s.push_str(", welcome to Rust!"); // modify through mutable ref } fn main() { let mut message = String::from("Hello"); append_greeting(&mut message); println!("{}", message); // "Hello, welcome to Rust!" } ``` Zmienna musi być zadeklarowana jako `mut`, aby można było utworzyć do niej referencję mutowalną. Zarówno deklaracja zmiennej, jak i tworzenie referencji wymagają jawnego oznaczenia mutowalności -- Rust wymusza świadome podejmowanie decyzji o modyfikowalności danych. ```rust // exclusivity_rule.rs fn main() { let mut data = String::from("shared state"); let r1 = &mut data; // let r2 = &mut data; // compile error: second mutable borrow println!("{}", r1); // After r1's last usage, a new mutable borrow is allowed let r3 = &mut data; // this works -- non-lexical lifetimes r3.push_str(" updated"); println!("{}", r3); } ``` Mechanizm non-lexical lifetimes (NLL) w Rust pozwala kompilatorowi śledzić, kiedy referencja jest faktycznie ostatnio używana, a nie kiedy wychodzi z zakresu leksykalnego. Dzięki temu nowe pożyczenie jest dozwolone natychmiast po ostatnim użyciu poprzedniej referencji, co znacząco poprawia ergonomię języka. ## Czasy życia referencji (lifetimes) Czasy życia to mechanizm, dzięki któremu kompilator sprawdza, czy referencje nie przeżyją danych, na które wskazują. W większości przypadków kompilator wnioskuje czasy życia automatycznie (lifetime elision), ale w sytuacjach niejednoznacznych wymagana jest jawna adnotacja. ```rust // lifetime_annotation.rs // 'a declares: the returned reference lives as long as // the shortest-lived input reference fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let result; let string1 = String::from("Rust ownership"); { let string2 = String::from("borrowing"); result = longest(string1.as_str(), string2.as_str()); println!("Longest: {}", result); // valid: both strings alive } // println!("{}", result); // would fail: string2 dropped } ``` Parametr `'a` nie zmienia czasu życia referencji -- informuje kompilator o relacji między czasami życia argumentów i wartości zwracanej. Funkcja `longest` deklaruje, że zwrócona referencja będzie żyła co najwyżej tak długo, jak krótsza z dwóch referencji wejściowych. To pozwala kompilatorowi wykryć potencjalne dangling references. ## Pożyczanie w strukturach (struct borrowing) Struktury przechowujące referencje wymagają jawnych adnotacji czasów życia. Adnotacja `'a` na strukturze gwarantuje, że pożyczone dane przeżyją instancję struktury. ```rust // struct_lifetime.rs struct Excerpt<'a> { text: &'a str, // this struct borrows a string slice } impl<'a> Excerpt<'a> { fn summary(&self) -> &str { let end = self.text.len().min(20); &self.text[..end] // return a slice of the borrowed text } } fn main() { let article = String::from("Rust ownership model eliminates memory bugs"); let excerpt = Excerpt { text: article.as_str(), }; println!("Summary: {}", excerpt.summary()); } ``` W praktyce struktury z referencjami są stosowane rzadziej niż struktury posiadające swoje dane (np. `String` zamiast `&str`). Niemniej zrozumienie tego wzorca jest niezbędne przy pracy z parserami, iteratorami oraz w kontekstach wymagających zero-copy przetwarzania danych. > **Typowe pułapki na rozmowach kwalifikacyjnych** > > Najczęstsze błędy kandydatów dotyczące ownership: (1) Próba użycia wartości po przeniesieniu -- należy użyć `clone()` lub referencji. (2) Mieszanie referencji mutowalnych i niemutowalnych w tym samym zakresie. (3) Zwracanie referencji do lokalnych zmiennych funkcji. (4) Nierozumienie różnicy między `&str` (wycinek) a `String` (typ posiadający dane). ## Wzorce ownership w praktyce W realnych projektach Rust pojawiają się powtarzalne wzorce zarządzania ownership. Trzy najczęstsze to: przejęcie ownership i zwrócenie nowej wartości, pożyczenie do odczytu oraz pożyczenie mutowalne do modyfikacji w miejscu. ```rust // ownership_patterns.rs // Pattern 1: Take ownership, return a new value fn process_and_return(mut input: String) -> String { input.push_str(" -- processed"); input // ownership transfers to caller } // Pattern 2: Borrow for read-only inspection fn contains_keyword(text: &str, keyword: &str) -> bool { text.to_lowercase().contains(&keyword.to_lowercase()) } // Pattern 3: Borrow mutably for in-place modification fn sanitize(input: &mut String) { *input = input.trim().to_string(); } fn main() { // Pattern 1 let raw = String::from("user input"); let processed = process_and_return(raw); // raw is now invalid, processed owns the data // Pattern 2 let found = contains_keyword(&processed, "input"); println!("Contains 'input': {}", found); // Pattern 3 let mut padded = String::from(" spaces everywhere "); sanitize(&mut padded); println!("Sanitized: '{}'", padded); } ``` Wybór między tymi wzorcami zależy od tego, czy funkcja potrzebuje własności danych, czy tylko dostępu do ich odczytu lub zapisu. Ogólna zasada brzmi: pożyczaj referencje, gdy to wystarczy, a przenoś ownership tylko wtedy, gdy jest to konieczne. ## Typowe błędy borrow checkera i ich rozwiązania Borrow checker generuje precyzyjne komunikaty o błędach, ale początkujący programiści Rust mogą mieć trudności z ich interpretacją. Poniżej przedstawiono trzy najczęstsze błędy wraz z idiomatycznymi rozwiązaniami. ```rust // common_fixes.rs fn main() { // Error E0502: cannot borrow as mutable because also borrowed as immutable let mut scores = vec![90, 85, 78]; // Fix: finish using the immutable borrow before mutating let first = scores[0]; // copy (i32 is Copy), no active borrow scores.push(95); // mutable borrow -- no conflict println!("First: {}, All: {:?}", first, scores); // Error E0382: use of moved value let name = String::from("Alice"); let greeting = format!("Hello, {}", name); // format! borrows, doesn't move println!("{} says {}", name, greeting); // both valid // Error E0597: borrowed value does not live long enough let outer; { let inner = String::from("temporary"); outer = inner; // move instead of borrow -- extends lifetime } println!("{}", outer); // works: outer owns the value } ``` Kluczem do rozwiązywania błędów borrow checkera jest analiza tego, kto jest właścicielem danych i jak długo żyją poszczególne referencje. W razie wątpliwości warto zacząć od klonowania wartości, a następnie zoptymalizować kod, eliminując zbędne kopie po zrozumieniu wzorców ownership. ## Podsumowanie System ownership i borrowing w Rust to potężny mechanizm gwarantujący bezpieczeństwo pamięci bez narzutu w runtime. Kluczowe zagadnienia do zapamiętania: - **Ownership** -- każda wartość ma jednego właściciela; przypisanie przenosi ownership dla typów nie-`Copy` - **Pożyczanie niemutowalne** (`&T`) -- dowolna liczba jednoczesnych referencji do odczytu - **Pożyczanie mutowalne** (`&mut T`) -- wyłączny dostęp do zapisu, maksymalnie jedna referencja - **Czasy życia** -- kompilator weryfikuje, że referencje nie przeżyją danych źródłowych - **Non-lexical lifetimes** -- referencje kończą się przy ostatnim użyciu, nie na końcu bloku - **Wzorce praktyczne** -- pożyczaj gdy to wystarczy, przenoś gdy to konieczne, klonuj jako ostateczność Opanowanie tych koncepcji nie tylko przygotowuje do rozmów kwalifikacyjnych z Rust, ale również buduje solidne fundamenty do pisania wydajnego i bezpiecznego kodu systemowego. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/rust/rust-ownership-borrowing-demystified