DaleSchool

빌림과 참조

중급20분

학습 목표

  • &를 사용하여 값을 빌릴 수 있다
  • 불변 참조와 가변 참조의 차이를 설명할 수 있다
  • 가변 참조는 한 번에 하나만 존재할 수 있다는 규칙을 이해한다
  • 빌림 규칙 위반 시 발생하는 에러를 읽고 해결할 수 있다

동작하는 코드

이전 모듈에서 함수에 값을 넘기면 소유권이 이동된다고 배웠어요. clone()으로 해결할 수 있었지만, 매번 복사하는 건 비효율적이에요.

더 좋은 방법이 있어요 — 빌림(borrowing) 이에요!

fn print_message(msg: &String) {
    // msg는 빌려온 값 — 읽기만 가능
    println!("{msg}");
}

fn main() {
    let message = String::from("안녕하세요!");
    print_message(&message);     // 빌려주기
    println!("아직 사용 가능: {message}"); // 주인은 그대로!
}

&가 핵심이에요. &message는 "이 값을 빌려줄게"라는 뜻이고, 매개변수 msg: &String은 "빌린 값을 받을게"라는 뜻이에요.

직접 수정하기

  1. 모듈 09에서 에러가 났던 코드를 빌림으로 고쳐보세요.
fn print_fruits(fruits: Vec<&str>) {
    for f in fruits {
        println!("{f}");
    }
}

fn main() {
    let fruits = vec!["사과", "바나나"];
    print_fruits(fruits);
    println!("과일 수: {}", fruits.len()); // 에러!
}

힌트: 함수의 매개변수와 호출 부분에 &를 추가해보세요. for f in fruitsfor f in fruits로 바뀔 수 있어요.

  1. clone() 없이 같은 결과를 낼 수 있나요? 직접 확인해보세요!

"왜?" — 도서관 대출로 이해하기

빌림을 도서관 대출로 생각하면 쉬워요.

  • 도서관(주인)에서 책(값)을 빌려가면, 도서관은 여전히 책의 주인이에요
  • 빌린 사람은 책을 읽을 수 있지만, 페이지를 찢으면 안 돼요(불변 빌림)
  • 빌린 기간이 끝나면 반납해야 해요
fn calculate_length(s: &String) -> usize {
    s.len()
    // 함수가 끝나면 빌린 값이 반납돼요
}

fn main() {
    let word = String::from("Rust");
    let len = calculate_length(&word);
    println!("{word}의 길이: {len}");
}

&word로 빌려줬기 때문에 소유권은 이동하지 않아요. 함수가 끝나면 빌린 참조가 자동으로 반납돼요.

가변 빌림 — 편집 권한

읽기만 하는 게 아니라 수정도 하고 싶다면? &mut을 사용해요.

fn add_exclamation(s: &mut String) {
    s.push_str("!!!");
}

fn main() {
    let mut message = String::from("안녕");
    add_exclamation(&mut message);
    println!("{message}"); // "안녕!!!"
}

가변 빌림을 편집 권한으로 생각하면 돼요.

  • 읽기 권한(&): 여러 명이 동시에 가질 수 있어요
  • 편집 권한(&mut): 한 명만 가질 수 있어요

왜 편집 권한은 한 명만 가능할까요? 여러 사람이 동시에 같은 문서를 수정하면 내용이 꼬이잖아요. Rust가 이걸 컴파일할 때 막아줘요.

빌림 규칙 정리

  1. 불변 참조(&)는 여러 개 동시에 존재할 수 있다
  2. 가변 참조(&mut)는 하나만 존재할 수 있다
  3. 불변 참조와 가변 참조는 동시에 존재할 수 없다
fn main() {
    let mut data = String::from("hello");

    let r1 = &data;     // OK — 읽기 권한 1
    let r2 = &data;     // OK — 읽기 권한 2
    println!("{r1}, {r2}");

    let r3 = &mut data; // OK — 읽기 권한이 더 이상 사용되지 않으니까
    r3.push_str(" world");
    println!("{r3}");
}
error[E0502]: cannot borrow `data` as mutable because it is
              also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &data;
  |              ----- immutable borrow occurs here
5 |     let r2 = &data;
6 |     let r3 = &mut data;
  |              ^^^^^^^^^ mutable borrow occurs here
7 |     println!("{r1}, {r2}");
  |               --- immutable borrow later used here

해석: "data를 불변으로 빌려놓고, 동시에 가변으로 빌리려 했어요. 읽기 권한이 아직 사용 중인데 편집 권한을 줄 수 없어요."

해결: 불변 참조 사용이 끝난 뒤에 가변 참조를 만드세요.

더 알아보기: NLL — Rust가 똑똑해진 비결

Rust 2018부터 NLL(Non-Lexical Lifetimes) 이라는 기능이 도입됐어요. 참조가 마지막으로 사용된 지점까지만 유효한 것으로 판단해요.

fn main() {
    let mut data = String::from("hello");

    let r1 = &data;
    println!("{r1}");      // r1의 마지막 사용

    let r2 = &mut data;    // OK! r1은 이미 사용이 끝남
    r2.push_str(" world");
    println!("{r2}");
}

예전에는 이 코드도 에러였지만, 지금은 컴파일러가 "r1은 더 이상 안 쓰이네"라고 판단해서 가변 빌림을 허용해요.

연습 1 (쉬움). 아래 코드가 컴파일되도록 빌림을 사용해서 수정해보세요.

fn greet(name: String) {
    println!("안녕, {name}!");
}

fn main() {
    let name = String::from("지수");
    greet(name);
    greet(name); // 에러! name이 이동됨
}

힌트: 함수가 소유권을 가져가지 않도록 &를 사용하세요.

연습 2 (보통). 아래 코드의 에러를 고쳐보세요.

fn main() {
    let mut numbers = vec![1, 2, 3];
    let first = &numbers[0];
    numbers.push(4);
    println!("첫 번째: {first}");
}

힌트: numbers.push(4)는 벡터를 가변으로 빌려야 해요. 불변 참조 first를 사용한 뒤에 push를 하면 어떨까요?

연습 3 (도전). 아래 함수를 완성하세요. 문자열의 첫 번째 단어를 반환해야 해요.

fn first_word(s: &String) -> &str {
    // 여기를 완성하세요!
    // 힌트: s.find(' ')와 &s[..index]를 사용해보세요
}

fn main() {
    let sentence = String::from("hello world");
    let word = first_word(&sentence);
    println!("첫 단어: {word}");
}

Q1. & 기호의 의미는?

  • A) 값을 복사한다
  • B) 값의 참조(빌림)를 만든다
  • C) 값을 삭제한다
  • D) 값의 타입을 바꾼다

Q2. 아래 코드에서 에러가 나는 이유는?

fn main() {
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &mut s;
    println!("{r1}");
}
  • A) String은 빌릴 수 없다
  • B) 불변 참조와 가변 참조가 동시에 존재한다
  • C) mut을 두 번 사용했다
  • D) println!에 참조를 넣을 수 없다

Q3. 가변 참조(&mut)는 동시에 몇 개까지 만들 수 있나요?

  • A) 제한 없음
  • B) 2개
  • C) 1개
  • D) 0개 (만들 수 없음)