동작하는 코드
이전 모듈에서 함수에 값을 넘기면 소유권이 이동된다고 배웠어요. clone()으로 해결할 수 있었지만, 매번 복사하는 건 비효율적이에요.
더 좋은 방법이 있어요 — 빌림(borrowing) 이에요!
fn print_message(msg: &String) {
// msg는 빌려온 값 — 읽기만 가능
println!("{msg}");
}
fn main() {
let message = String::from("안녕하세요!");
print_message(&message); // 빌려주기
println!("아직 사용 가능: {message}"); // 주인은 그대로!
}
&가 핵심이에요. &message는 "이 값을 빌려줄게"라는 뜻이고, 매개변수 msg: &String은 "빌린 값을 받을게"라는 뜻이에요.
직접 수정하기
- 모듈 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 fruits도 for f in fruits로 바뀔 수 있어요.
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가 이걸 컴파일할 때 막아줘요.
빌림 규칙 정리
- 불변 참조(
&)는 여러 개 동시에 존재할 수 있다- 가변 참조(
&mut)는 하나만 존재할 수 있다- 불변 참조와 가변 참조는 동시에 존재할 수 없다
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개 (만들 수 없음)