# Ownership e Borrowing in Rust: Guida Completa > Padroneggia il sistema di ownership e borrowing di Rust. Regole di proprietà, riferimenti, lifetime e pattern avanzati di gestione della memoria. - Published: 2026-02-20 - Updated: 2026-04-29 - Author: SharpSkill - Tags: rust, ownership, borrowing, memory management, systems programming - Reading time: 18 min --- Il sistema di ownership è ciò che distingue Rust da qualsiasi altro linguaggio di programmazione. Questo approccio unico garantisce la sicurezza della memoria senza garbage collector, intercettando i bug in fase di compilazione anziché in esecuzione. Questa guida approfondita esplora i meccanismi di ownership e borrowing, dai fondamenti ai pattern avanzati di produzione. > **Filosofia di Rust** > > Il compilatore Rust agisce come un assistente di programmazione esigente: ogni errore di ownership bloccato in fase di compilazione rappresenta un potenziale bug evitato in produzione. ## Le Tre Regole Fondamentali dell'Ownership Il sistema di ownership si basa su tre regole semplici ma rigorose. Una volta interiorizzate, il modello mentale di Rust diventa naturale e prevedibile. ```rust // ownership_rules.rs // Demonstration of the three fundamental rules fn main() { // Rule 1: Each value has exactly ONE owner let s1 = String::from("hello"); // s1 is the sole owner // Rule 2: There can only be one owner at a time let s2 = s1; // Ownership transferred (moved) from s1 to s2 // println!("{}", s1); // Compile ERROR: s1 no longer exists println!("s2 = {}", s2); // Only s2 is valid now // Rule 3: When the owner goes out of scope, the value is dropped { let s3 = String::from("temporary"); println!("s3 inside block = {}", s3); } // s3 is automatically freed here (drop is called) // println!("{}", s3); // ERROR: s3 no longer exists } ``` Queste tre regole eliminano intere categorie di bug: use-after-free, double-free e memory leak. Il compilatore verifica staticamente che queste regole siano rispettate. ## Move vs Copy: Comprendere la Semantica del Trasferimento Il comportamento dell'assegnazione dipende dal tipo di dato. I tipi che implementano il trait Copy vengono duplicati, mentre gli altri vengono trasferiti tramite move. ```rust // move_vs_copy.rs // Distinction between Copy types and Move types fn main() { // Copy types: values stored on the stack, known size let x: i32 = 42; let y = x; // x is COPIED, not moved println!("x = {}, y = {}", x, y); // Both are valid // Other Copy types: f64, bool, char, tuples of Copy types let point = (3.0, 4.0); let point_copy = point; // Tuple copy println!("Original: {:?}, Copy: {:?}", point, point_copy); // Move types: values on the heap, dynamic size let s1 = String::from("owned"); let s2 = s1; // s1 is MOVED to s2 // println!("{}", s1); // ERROR: value moved println!("s2 = {}", s2); // Vec, HashMap, Box are also Move types let vec1 = vec![1, 2, 3]; let vec2 = vec1; // Move, not copy // println!("{:?}", vec1); // ERROR println!("vec2 = {:?}", vec2); } // Explicit clone to duplicate Move types fn explicit_clone() { let original = String::from("important data"); let clone = original.clone(); // Explicit duplication (memory cost) println!("Original: {}", original); // Still valid println!("Clone: {}", clone); // Independent copy } ``` La distinzione Move/Copy è fondamentale: determina se l'assegnazione trasferisce la proprietà o crea una copia indipendente. > **Quando Usare Clone** > > Chiamare `.clone()` deve essere intenzionale. Un codice pieno di clone può indicare un problema di design. Il borrowing è spesso una soluzione migliore. ## Borrowing: Riferimenti Immutabili e Mutabili Il borrowing permette di accedere a un valore senza prenderne la proprietà. Questo meccanismo rende il codice Rust al tempo stesso sicuro e performante. ```rust // borrowing_basics.rs // Immutable and mutable references fn main() { let s = String::from("hello"); // Immutable reference: read-only, multiple allowed let len = calculate_length(&s); // Immutable borrow println!("'{}' has {} characters", s, len); // s still valid // Multiple simultaneous immutable references: OK let r1 = &s; let r2 = &s; let r3 = &s; println!("r1={}, r2={}, r3={}", r1, r2, r3); } fn calculate_length(s: &String) -> usize { // s is a reference, not the owner s.len() } // s goes out of scope but doesn't drop anything (not owner) // Mutable references: modification allowed fn mutable_borrowing() { let mut s = String::from("hello"); change(&mut s); // Mutable borrow println!("After modification: {}", s); } fn change(s: &mut String) { s.push_str(", world!"); // Modification via mutable reference } ``` La regola d'oro del borrowing: o riferimenti immutabili multipli OPPURE un singolo riferimento mutabile, mai entrambi simultaneamente. ## Regole del Borrow Checker Il borrow checker è il componente del compilatore che verifica le regole di borrowing. Comprenderne gli errori permette di risolvere rapidamente i problemi. ```rust // borrow_checker_rules.rs // Strict borrow checker rules fn main() { // RULE 1: No mutable reference with immutable references let mut s = String::from("hello"); let r1 = &s; // Immutable reference: OK let r2 = &s; // Another immutable reference: OK // let r3 = &mut s; // ERROR: cannot borrow as mutable println!("{} and {}", r1, r2); // AFTER using r1 and r2, they are "dead" let r3 = &mut s; // Now OK: r1 and r2 no longer used r3.push_str(" world"); println!("{}", r3); // RULE 2: Only one mutable reference at a time let mut data = String::from("exclusive"); let ref1 = &mut data; // let ref2 = &mut data; // ERROR: already borrowed mutably ref1.push_str("!"); println!("{}", ref1); } // RULE 3: References cannot outlive the data fn dangling_reference_prevented() { let reference; { let s = String::from("short-lived"); // reference = &s; // ERROR: s doesn't live long enough } // s is dropped here, reference would be invalid // Solution: move the value out of the scope let owned_outside; { let s = String::from("moved out"); owned_outside = s; // Move, not reference } println!("{}", owned_outside); // OK: owned_outside is the owner } ``` Il borrow checker utilizza i Non-Lexical Lifetimes (NLL): un riferimento è considerato attivo solo fino al suo ultimo utilizzo, non fino alla fine dello scope. ## Lifetime: Annotare la Durata dei Riferimenti I lifetime sono annotazioni che aiutano il compilatore a verificare che i riferimenti rimangano validi. Nella maggior parte dei casi vengono dedotti automaticamente. ```rust // lifetimes_explained.rs // Explicit lifetime annotations // Without annotation: compiler infers lifetimes fn first_word(s: &str) -> &str { match s.find(' ') { Some(i) => &s[..i], None => s, } } // With explicit annotation: same function fn first_word_explicit<'a>(s: &'a str) -> &'a str { // 'a means: returned reference lives as long as the input match s.find(' ') { Some(i) => &s[..i], None => s, } } // When annotations are necessary: multiple references fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { // Compiler cannot guess which reference is returned if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string"); let result; { let string2 = String::from("xyz"); result = longest(&string1, &string2); println!("Longest: {}", result); // OK here } // println!("{}", result); // ERROR if uncommented: string2 dropped } ``` I lifetime non cambiano la durata di vita dei dati; descrivono le relazioni tra le durate di vita di riferimenti diversi. ## Lifetime nelle Struct Quando una struct contiene riferimenti, i lifetime devono essere annotati per garantire che la struct non sopravviva ai dati referenziati. ```rust // struct_lifetimes.rs // Structs containing references // Struct with reference: lifetime required struct ImportantExcerpt<'a> { part: &'a str, // This reference must live at least as long as the struct } impl<'a> ImportantExcerpt<'a> { // Method returning a reference with the same lifetime fn level(&self) -> i32 { 3 } // Elision rule: &self implies the output lifetime fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention: {}", announcement); self.part // Returns with 'a lifetime from self } } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().unwrap(); let excerpt = ImportantExcerpt { part: first_sentence, // OK: novel outlives excerpt }; println!("Excerpt: {}", excerpt.part); println!("Level: {}", excerpt.level()); } // Static lifetime: reference valid for the entire program duration fn static_lifetime_example() { let s: &'static str = "This string is in the binary"; // String literals always have 'static lifetime println!("{}", s); } ``` Le regole di elisione dei lifetime spesso permettono di omettere le annotazioni nei casi comuni, rendendo il codice più leggibile. ## Pattern Avanzati: Mutabilità Interna A volte la mutabilità deve essere verificata in fase di esecuzione anziché di compilazione. Rust fornisce tipi per questo pattern: RefCell e Cell. ```rust // interior_mutability.rs // Interior mutability with RefCell and Cell use std::cell::{Cell, RefCell}; // Cell: for Copy types, replaces the entire value struct Counter { count: Cell, // Mutable despite &self } impl Counter { fn new() -> Counter { Counter { count: Cell::new(0) } } fn increment(&self) { // Modification via immutable reference! self.count.set(self.count.get() + 1); } fn get(&self) -> u32 { self.count.get() } } // RefCell: for non-Copy types, checks at runtime struct CachedValue { value: RefCell>, } impl CachedValue { fn new() -> CachedValue { CachedValue { value: RefCell::new(None) } } fn get_or_compute(&self, compute: impl FnOnce() -> String) -> String { // borrow() for reading, borrow_mut() for writing if self.value.borrow().is_none() { *self.value.borrow_mut() = Some(compute()); } self.value.borrow().as_ref().unwrap().clone() } } fn main() { let counter = Counter::new(); counter.increment(); counter.increment(); println!("Counter: {}", counter.get()); // 2 let cache = CachedValue::new(); let result = cache.get_or_compute(|| { println!("Expensive computation..."); String::from("result") }); println!("Value: {}", result); // Second call: no recomputation let result2 = cache.get_or_compute(|| String::from("never executed")); println!("Cache hit: {}", result2); } ``` RefCell e Cell spostano la verifica del borrowing al runtime. Una violazione di regola provoca un panic invece di un errore di compilazione. > **Attenzione ai Panic** > > `RefCell::borrow_mut()` provoca un panic se il valore è già preso in prestito. Conviene usare `try_borrow_mut()` per una gestione esplicita degli errori. ## Smart Pointer e Ownership Gli smart pointer come Box, Rc e Arc offrono diverse strategie di ownership per casi d'uso specifici. ```rust // smart_pointers.rs // Box, Rc, and Arc for different ownership patterns use std::rc::Rc; use std::sync::Arc; use std::thread; // Box: single owner, data on the heap fn box_example() { let boxed = Box::new(vec![1, 2, 3, 4, 5]); println!("Boxed vec: {:?}", boxed); // Useful for: recursive types, large objects, trait objects } // Rc: reference counting, multiple owners (single-thread) fn rc_example() { let data = Rc::new(String::from("shared data")); let clone1 = Rc::clone(&data); // Increments the counter let clone2 = Rc::clone(&data); println!("Count: {}", Rc::strong_count(&data)); // 3 println!("All share: {}, {}, {}", data, clone1, clone2); } // Freed when counter reaches 0 // Arc: thread-safe Rc (Atomic Reference Counting) fn arc_example() { let data = Arc::new(vec![1, 2, 3]); let handles: Vec<_> = (0..3).map(|i| { let data_clone = Arc::clone(&data); thread::spawn(move || { println!("Thread {}: {:?}", i, data_clone); }) }).collect(); for handle in handles { handle.join().unwrap(); } } fn main() { box_example(); rc_example(); arc_example(); } ``` La scelta dello smart pointer dipende dal pattern di ownership: unico (Box), condiviso single-thread (Rc) o condiviso multi-thread (Arc). ## Pattern Pratici di Ownership Di seguito alcuni pattern comuni per strutturare il codice attorno al sistema di ownership. ```rust // ownership_patterns.rs // Practical patterns for ownership management // Pattern 1: Builder pattern with chained ownership struct RequestBuilder { url: String, headers: Vec<(String, String)>, timeout: Option, } impl RequestBuilder { fn new(url: &str) -> Self { RequestBuilder { url: url.to_string(), headers: Vec::new(), timeout: None, } } // Consumes self and returns the new self fn header(mut self, key: &str, value: &str) -> Self { self.headers.push((key.to_string(), value.to_string())); self // Returns ownership } fn timeout(mut self, seconds: u64) -> Self { self.timeout = Some(seconds); self } fn build(self) -> Request { Request { url: self.url, headers: self.headers, timeout: self.timeout.unwrap_or(30), } } } struct Request { url: String, headers: Vec<(String, String)>, timeout: u64, } // Pattern 2: Cow (Copy-on-Write) to avoid allocations use std::borrow::Cow; fn process_text(input: &str) -> Cow { if input.contains("REPLACE") { // Allocation only if modification needed Cow::Owned(input.replace("REPLACE", "NEW")) } else { // No allocation, returns a reference Cow::Borrowed(input) } } // Pattern 3: Take to extract from an Option fn extract_value(data: &mut Option) -> String { data.take().unwrap_or_else(|| String::from("default")) // take() replaces with None and returns ownership of the value } fn main() { // Builder pattern let request = RequestBuilder::new("https://api.example.com") .header("Authorization", "Bearer token") .header("Content-Type", "application/json") .timeout(60) .build(); println!("URL: {}, Timeout: {}s", request.url, request.timeout); // Cow pattern let text1 = process_text("hello world"); // No allocation let text2 = process_text("hello REPLACE"); // Allocation println!("{} | {}", text1, text2); // Take pattern let mut optional = Some(String::from("extracted")); let value = extract_value(&mut optional); println!("Value: {}, Option: {:?}", value, optional); // None } ``` Questi pattern sfruttano il sistema di ownership per creare API ergonomiche e performanti. ## Conclusione Il sistema di ownership e borrowing di Rust rappresenta un cambio di paradigma nella gestione della memoria. Una volta padroneggiato, diventa un alleato potente per scrivere codice al tempo stesso performante e sicuro. **Punti chiave da ricordare:** ✅ Tre regole di ownership: proprietario unico, trasferimento di proprietà, drop automatico ✅ Borrowing: riferimenti immutabili multipli OPPURE un riferimento mutabile esclusivo ✅ Lifetime: annotare le relazioni tra le durate dei riferimenti ✅ Mutabilità interna: RefCell e Cell per mutabilità verificata a runtime ✅ Smart pointer: Box (unico), Rc (condiviso), Arc (thread-safe) ✅ Pattern pratici: Builder, Cow, Take per API idiomatiche Il borrow checker può sembrare rigido all'inizio, ma ogni errore segnalato rappresenta un potenziale bug evitato. Con la pratica, ragionare in termini di ownership diventa naturale e migliora la qualità del codice in tutti i linguaggi. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/rust/ownership-borrowing-rust-complete-guide