본문으로 건너뛰기
Previous
Next
Rust Ownership | Ownership, Borrowing, and Lifetimes

Rust Ownership | Ownership, Borrowing, and Lifetimes

Rust Ownership | Ownership, Borrowing, and Lifetimes

이 글의 핵심

Deep dive into Rust ownership: move and copy, functions and ownership, references, slices, lifetime annotations, and patterns compared to C++—with runnable examples.

Introduction

Ownership is Rust’s signature feature: memory safety without a garbage collector by enforcing rules at compile time. Think of each heap value as a single key to an apartment—only one variable holds the key; when it goes out of scope, Rust locks the door (drops) exactly once. There is no duplicate key that could double-free. Compare with C++ [smart pointers](/en/blog/cpp-comparison-04-shared-unique-ptr/ and Python’s GC-backed [objects](/en/blog/python-series-03-data-types/.

1. Ownership rules

Rule 1: Every value has an owner

fn main() {
    let s = String::from("hello");
    // `s` owns the heap-allocated string
}

What this means: After String::from allocates on the heap, s is the sole owner of that memory. When s leaves scope, Rust calls drop—no manual free like in C.

Rule 2: Only one owner

Each value has exactly one owner at a time:

fn main() {
    let s1 = String::from("hello");
    
    // Move: ownership transfers fully to s2
    // s1 is no longer valid
    let s2 = s1;
    
    // println!("{}", s1);  // Compile error!
    // "value borrowed here after move"
    
    println!("{}", s2);  // OK — s2 owns the data
}
// When s2 goes out of scope, the string is dropped once

Why move? C++ pitfall:

// Shallow copy can lead to double free if not careful
std::string s1 = "hello";
std::string s2 = s1;  // Both may share resources depending on implementation
// Destructors can run twice on the same resource if misdesigned

Perspective: In C++, if you do not consistently use value semantics, it is easy to get copy/move/destructor rules wrong. Rust defaults to move and makes expensive copies explicit with clone(). Rust’s approach:

  • Deep copies are costly (allocation + memcpy)
  • By default, ownership moves (transfer + invalidate source)
  • Double free is impossible (single owner)
  • Need a duplicate? Call clone() explicitly
let s1 = String::from("hello");
let s2 = s1.clone();  // Deep copy (explicit)
println!("{}, {}", s1, s2);  // Both OK

clone in production: It can be CPU- and memory-heavy—profile hot loops and avoid redundant clones. Sometimes you choose clone() anyway to simplify APIs when correctness matters more than micro-optimization. The Copy trait:

// Small stack types (integers, bool, etc.) implement Copy
// Assignment copies instead of moving
let x = 5;
let y = x;  // Copy, not move
println!("{}, {}", x, y);  // Both OK
// Cheap to copy: fixed small size on the stack

Rule 3: Drop at end of scope

fn main() {
    {
        let s = String::from("hello");
        println!("{}", s);
    }  // `drop` runs for `s` here
    
    // println!("{}", s);  // Error: s is gone
}

RAII: Use the same pattern for resources (files, locks, connections) scoped to a block—cleanup runs automatically. This ties into the Drop trait and idiomatic resource management in Rust.

2. Functions and ownership

Moving into a function

fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    
    // println!("{}", s);  // Error: ownership moved
}
fn takes_ownership(s: String) {
    println!("{}", s);
}  // `s` is dropped here

Passing by value: Giving a String to a function consumes it for the caller. Use .clone() or references (&String / &str) if the caller must keep using it. Many codebases distinguish consuming APIs from borrowing APIs by naming and signatures.

Returning ownership

fn main() {
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);
    
    println!("{}, {}", s1, s3);
}
fn gives_ownership() -> String {
    String::from("hello")
}
fn takes_and_gives_back(s: String) -> String {
    s
}

3. References and borrowing

Immutable references (&)

fn main() {
    let s1 = String::from("hello");
    
    let len = calculate_length(&s1);
    println!("{} length: {}", s1, len);  // s1 still usable
}
fn calculate_length(s: &String) -> usize {
    s.len()
}  // s is a reference; nothing is dropped here

Mutable references (&mut)

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s);  // hello, world
}
fn change(s: &mut String) {
    s.push_str(", world");
}

The borrowing rules

Rust’s rules prevent data races at compile time:

fn main() {
    let mut s = String::from("hello");
    
    // Multiple immutable borrows are allowed
    let r1 = &s;
    let r2 = &s;
    println!("{}, {}", r1, r2);  // OK
    
    // Only one mutable borrow at a time
    let r3 = &mut s;
    // let r4 = &mut s;  // Error!
    println!("{}", r3);  // OK
    
    // Cannot mix & and &mut in the same live region
    let r5 = &s;
    // let r6 = &mut s;  // Error!
    println!("{}", r5);
}

4. Slices

A slice references a contiguous portion of a collection:

fn main() {
    let s = String::from("hello world");
    
    let hello = &s[0..5];   // "hello" as &str
    let world = &s[6..11];  // "world"
    
    println!("{}, {}", hello, world);
    
    let hello2 = &s[..5];
    let world2 = &s[6..];
    let full = &s[..];
}
// First word: slice example
fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    
    &s[..]
}
fn main() {
    let sentence = String::from("hello world");
    let word = first_word(&sentence);
    println!("first word: {}", word);
    
    // If `word` borrows `sentence`, mutating `sentence` can fail to compile:
    // let mut s = String::from("hello world");
    // let word = first_word(&s);
    // s.clear();  // Error while `word` is live
}

Slice types:

let s: String = String::from("hello");
let slice: &str = &s[0..2];  // "he"
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3];  // [2, 3]
// Slice = pointer + length; out-of-range access panics

5. Lifetimes

Lifetime annotations

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
fn main() {
    let s1 = String::from("long string");
    let s2 = String::from("short");
    
    let result = longest(&s1, &s2);
    println!("longest: {}", result);
}

Struct lifetimes

struct ImportantExcerpt<'a> {
    part: &'a str,
}
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,
    };
    
    println!("{}", excerpt.part);
}

6. Hands-on example

String processing

fn main() {
    let text = String::from("hello rust world");
    
    let words = split_words(&text);
    println!("words: {:?}", words);
    
    let first = first_word(&text);
    println!("first word: {}", first);
}
fn split_words(s: &String) -> Vec<&str> {
    s.split_whitespace().collect()
}
fn first_word(s: &String) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

Summary

Takeaways

  1. Ownership: one owner per value
  2. Move: transfer ownership
  3. Borrowing: use references (&, &mut)
  4. Lifetimes: how long references stay valid
  5. Safety: enforced at compile time

Next steps


Compared to other languages

  • C++ move semantics | Rvalue references and std::move


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Deep dive into Rust ownership: move and copy, functions and ownership, references, slices, lifetime annotations, and pat… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • [Getting Started with Rust | Memory-Safe Systems Programming](/en/blog/rust-series-01-intro/
  • [Rust Memory Safety: Ownership, Borrowing, Lifetimes, unsafe](/en/blog/cpp-series-47-3-rust-memory-safety/
  • [C++ shared_ptr vs unique_ptr: Smart Pointer Choice Complete](/en/blog/cpp-comparison-04-shared-unique-ptr/
  • [Python Data Types | Lists· Dictionaries](/en/blog/python-series-03-data-types/

이 글에서 다루는 키워드 (관련 검색어)

Rust, Ownership, Borrowing, Lifetimes, Memory safety 등으로 검색하시면 이 글이 도움이 됩니다.