# Ownership e Borrowing em Rust: Guia Completo > Domine o sistema de ownership e borrowing do Rust. Regras de propriedade, referências, lifetimes e padrões avançados de gerenciamento de memória. - Published: 2026-02-20 - Updated: 2026-04-29 - Author: SharpSkill - Tags: rust, ownership, borrowing, memory management, systems programming - Reading time: 18 min --- O sistema de ownership é o que diferencia o Rust de qualquer outra linguagem de programação. Essa abordagem única garante segurança de memória sem coletor de lixo, capturando bugs em tempo de compilação em vez de em tempo de execução. Este guia aprofundado explora os mecanismos de ownership e borrowing, dos fundamentos aos padrões avançados de produção. > **Filosofia do Rust** > > O compilador do Rust age como um assistente de programação exigente: cada erro de ownership bloqueado em tempo de compilação representa um bug potencial evitado em produção. ## As Três Regras Fundamentais do Ownership O sistema de ownership repousa sobre três regras simples mas estritas. Uma vez interiorizadas, o modelo mental do Rust torna-se natural e previsível. ```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 } ``` Essas três regras eliminam categorias inteiras de bugs: use-after-free, double-free e vazamentos de memória. O compilador verifica estaticamente que essas regras são respeitadas. ## Move vs Copy: Compreender a Semântica de Transferência O comportamento da atribuição depende do tipo de dado. Os tipos que implementam o trait Copy são duplicados, enquanto os outros sofrem 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 } ``` A distinção Move/Copy é fundamental: determina se a atribuição transfere a propriedade ou cria uma cópia independente. > **Quando Usar Clone** > > Chamar `.clone()` deve ser intencional. Um código repleto de clones pode indicar um problema de design. O borrowing costuma ser uma solução melhor. ## Borrowing: Referências Imutáveis e Mutáveis O borrowing permite acessar um valor sem tomar a sua propriedade. Esse mecanismo torna o código Rust seguro e performático ao mesmo tempo. ```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 } ``` A regra de ouro do borrowing: ou múltiplas referências imutáveis OU uma única referência mutável, nunca ambas simultaneamente. ## Regras do Borrow Checker O borrow checker é o componente do compilador que verifica as regras de borrowing. Compreender seus erros permite resolver problemas rapidamente. ```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 } ``` O borrow checker utiliza Non-Lexical Lifetimes (NLL): uma referência é considerada ativa apenas até seu último uso, não até o fim do escopo. ## Lifetimes: Anotar a Duração das Referências Lifetimes são anotações que ajudam o compilador a verificar que as referências permanecem válidas. Na maioria das vezes, são inferidas 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 } ``` Lifetimes não alteram quanto tempo os dados vivem; descrevem relações entre os tempos de vida de diferentes referências. ## Lifetimes em Structs Quando uma struct contém referências, os lifetimes devem ser anotados para garantir que a struct não sobreviva aos dados referenciados. ```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); } ``` As regras de elisão de lifetimes geralmente permitem omitir as anotações em casos comuns, tornando o código mais legível. ## Padrões Avançados: Mutabilidade Interior Às vezes a mutabilidade precisa ser verificada em tempo de execução em vez de em tempo de compilação. O Rust fornece tipos para esse padrão: 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 movem a verificação do borrowing para o tempo de execução. Uma violação de regra causa um panic em vez de um erro de compilação. > **Atenção aos Panics** > > `RefCell::borrow_mut()` causa panic se o valor já estiver emprestado. Convém usar `try_borrow_mut()` para um tratamento explícito de erros. ## Smart Pointers e Ownership Smart pointers como Box, Rc e Arc oferecem diferentes estratégias de ownership para casos de uso específicos. ```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(); } ``` A escolha do smart pointer depende do padrão de ownership: único (Box), compartilhado em uma única thread (Rc) ou compartilhado entre threads (Arc). ## Padrões Práticos de Ownership A seguir, padrões comuns para estruturar código em torno do sistema de 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 } ``` Esses padrões aproveitam o sistema de ownership para criar APIs ergonômicas e performáticas. ## Conclusão O sistema de ownership e borrowing do Rust representa uma mudança de paradigma no gerenciamento de memória. Uma vez dominado, torna-se um aliado poderoso para escrever código eficiente e seguro ao mesmo tempo. **Pontos-chave a reter:** ✅ Três regras de ownership: proprietário único, transferência de propriedade, drop automático ✅ Borrowing: múltiplas referências imutáveis OU uma referência mutável exclusiva ✅ Lifetimes: anotar relações entre os tempos de vida das referências ✅ Mutabilidade interior: RefCell e Cell para mutabilidade verificada em runtime ✅ Smart pointers: Box (único), Rc (compartilhado), Arc (thread-safe) ✅ Padrões práticos: Builder, Cow, Take para APIs idiomáticas O borrow checker pode parecer rígido no início, mas cada erro que sinaliza representa um bug potencial evitado. Com a prática, pensar em termos de ownership torna-se natural e melhora a qualidade do código em todas as linguagens. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/rust/ownership-borrowing-rust-complete-guide