Day 6-8: References & Borrowing 요약

Day 6-8: References & Borrowing 요약

🎯 핵심 개념

왜 필요한가?

Day 3-5에서 발견한 문제: 함수에 값을 넘기면 소유권이 이동 → 원래 변수 사용 불가

fn takes_ownership(s: String) { println!("{}", s); }
 
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s);  // ❌ 소유권 이동됨!

해결책: 소유권을 넘기지 않고 빌려주기(Borrowing)


📌 두 종류의 참조

1. Immutable Reference (&T) — 읽기 전용

fn calculate_length(s: &String) -> usize {
    s.len()  // 읽기만 가능
}
 
let s = String::from("hello");
let len = calculate_length(&s);  // 빌려줌
println!("{}", s);               // ✅ 여전히 사용 가능

2. Mutable Reference (&mut T) — 수정 가능

fn change(some_string: &mut String) {
    some_string.push_str(", world");  // 수정 가능
}
 
let mut s = String::from("hello");  // 원본도 mut이어야 함!
change(&mut s);                      // mutable로 빌려줌
println!("{}", s);                   // "hello, world"

Python과의 차이

PythonRust &TRust &mut T
읽기
수정

Python은 참조를 통해 마음대로 수정 가능하지만, Rust는 명시적으로 &mut을 써야만 수정 가능


📌 Borrowing 핵심 규칙 (2가지)

규칙 1: &mut T는 동시에 1개만

let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;  // ❌ 두 번째 mutable 참조 불가!
println!("{}, {}", r1, r2);

이유: 두 곳에서 동시에 수정하면 데이터 충돌

규칙 2: &T&mut T 동시 사용 불가

let mut s = String::from("hello");
let r1 = &s;       // 📖 읽기
let r2 = &s;       // 📖 읽기
let r3 = &mut s;   // ❌ 읽는 중에 수정 불가!
println!("{}, {}, {}", r1, r2, r3);

이유: 읽는 중에 데이터가 변경되면 위험

예외: &T 여러 개는 OK

let s = String::from("hello");
let r1 = &s;  // ✅
let r2 = &s;  // ✅
let r3 = &s;  // ✅ 읽기끼리는 안전!

도서관 비유

  • &T (읽기) = 여러 명이 동시에 책 읽기 → OK
  • &mut T (수정) = 한 명만 펜으로 수정 가능 → 동시에 두 명 불가
  • &T + &mut T = 읽는 중에 수정 → 읽던 내용이 바뀔 위험

📌 NLL (Non-Lexical Lifetimes)

Rust 컴파일러는 참조의 마지막 사용 시점을 기준으로 판단:

let mut s = String::from("hello");
 
let r1 = &s;                         // ── r1 시작
let r2 = &s;                         // ── r2 시작
println!("{}, {}", r1, r2);          // ── r1, r2 끝 (마지막 사용)
 
let r3 = &mut s;                     // ✅ r1, r2 이미 끝났으니 OK!
r3.push_str(", world");
println!("{}", r3);

핵심: 읽기가 끝난 후 수정하는 건 안전 → 컴파일러가 허용


📌 Dangling Reference 방지

// ❌ 함수 내부 변수의 참조를 반환 → s가 drop되면 무효!
fn dangle() -> &String {
    let s = String::from("hello");
    &s  // s가 함수 끝에서 사라짐 → 가리킬 대상 없음!
}
 
// ✅ 소유권 자체를 반환
fn no_dangle() -> String {
    let s = String::from("hello");
    s  // 소유권이 호출자에게 이동
}

C/C++에서는 dangling pointer가 런타임 버그로 이어지지만, Rust는 컴파일 단계에서 차단


📌 실전 패턴: 함수에서의 Borrowing

fn average(scores: &Vec<f64>) -> f64 {       // & → 읽기만
    let sum = scores.iter().sum::<f64>();
    sum / scores.len() as f64
}
 
fn highest(scores: &Vec<f64>) -> f64 {        // & → 읽기만
    *scores.iter()
        .max_by(|a, b| a.partial_cmp(b).unwrap())
        .unwrap()
}
 
fn add_score(scores: &mut Vec<f64>, score: f64) {  // &mut → 수정 필요
    scores.push(score);
}
 
fn main() {
    let mut scores = vec![85.0, 92.0, 78.0, 95.0, 88.0];
    
    println!("평균: {}", average(&scores));       // 읽기
    println!("최고: {}", highest(&scores));       // 읽기
    add_score(&mut scores, 100.0);                // 수정
    println!("추가 후 평균: {}", average(&scores)); // 다시 읽기
    println!("전체 점수: {:?}", scores);           // 소유권 유지됨!
}

원칙: 수정이 필요한 함수만 &mut, 나머지는 &최소 권한 부여


📊 전체 규칙 요약표

규칙내용이유
&T 여러 개✅ 동시 OK읽기끼리는 안전
&mut T 1개만❌ 동시에 2개 불가동시 수정 → 충돌 방지
&T + &mut T❌ 동시 불가읽는 중 수정 → 데이터 깨짐 방지
NLL마지막 사용 시점 기준불필요한 제약 완화
Dangling 금지사라진 데이터 참조 불가무효 메모리 접근 방지

✅ 마스터 체크리스트

  • "한 번에 하나의 mutable reference만" 규칙의 이유 설명
  • "mutable과 immutable reference 동시 사용 불가" 이유 설명
  • Dangling reference가 왜 위험한지, Rust는 어떻게 방지하는지
  • 컴파일 에러 메시지를 보고 borrowing 문제 파악 가능
  • Reference가 언제 "drop"되는지 이해 (NLL)

🔗 참고 자료


🚀 다음 단계

Day 9-10: Structs & Methods&self, &mut self 패턴으로 오늘 배운 borrowing을 구조체에 적용