Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

9. 스마트 포인터와 내부 가변성 🟡

학습 목표:

  • 힙 할당과 소유권 공유를 위한 Box, Rc, Arc의 차이점을 마스터합니다.
  • 순환 참조를 끊기 위한 약한 참조(Weak reference) 활용법을 배웁니다.
  • Cell, RefCell, Cow를 통한 내부 가변성 패턴을 익힙니다.
  • 자기 참조 타입을 위한 Pin과 생명주기 제어를 위한 ManuallyDrop을 이해합니다.

Box, Rc, Arc: 힙 할당과 공유

#![allow(unused)]
fn main() {
// --- Box<T>: 단일 소유자, 힙 할당 ---
// 용도: 재귀적 타입, 큰 데이터 전송, 트레이트 객체
let boxed = Box::new(42);

// --- Rc<T>: 다중 소유자, 싱글 스레드 전용 ---
// 용도: 같은 데이터를 여러 곳에서 읽어야 할 때 (참조 카운팅)
let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a); // 카운트만 증가 (깊은 복사 아님)

// --- Arc<T>: 다중 소유자, 스레드 안전 ---
// 용도: 여러 스레드 간에 데이터를 공유할 때
let shared = Arc::new(String::from("공유 데이터"));
}

약한 참조 (Weak Reference): 순환 참조 끊기

RcArc는 참조 카운트가 0이 되어야 메모리를 해제합니다. 두 객체가 서로를 강하게 참조하면 절대 해제되지 않는 메모리 누수가 발생합니다. 이때 약한 참조를 사용하여 소유권을 주장하지 않고 데이터에 접근할 수 있습니다.

#![allow(unused)]
fn main() {
struct Node {
    parent: RefCell<Weak<Node>>, // 부모는 자식을 소유하지만, 자식은 부모를 소유하지 않음
    children: RefCell<Vec<Rc<Node>>>,
}
}

Cell과 RefCell: 내부 가변성 (Interior Mutability)

불변 참조(&T) 뒤에 있는 데이터를 수정해야 할 때 사용하는 '탈출구'입니다.

  • Cell<T>: Copy 타입에 적합하며 런타임 오버헤드가 거의 없습니다. 값을 복사하거나 교체하는 방식으로 작동합니다.
  • RefCell<T>: 모든 타입에 사용 가능하며, 런타임에 대여 규칙을 검사합니다. 규칙 위반 시 패닉(Panic)이 발생합니다.

Cow: 쓰기 시 복사 (Clone on Write)

불필요한 할당을 줄이는 영리한 열거형입니다. 대부분의 경우 데이터를 빌려 쓰다가, 수정이 필요할 때만 비로소 복제를 수행합니다.

#![allow(unused)]
fn main() {
fn normalize(input: &str) -> Cow<'_, str> {
    if input.contains('\t') {
        // 탭이 있을 때만 새로운 String 할당
        Cow::Owned(input.replace('\t', "    "))
    } else {
        // 탭이 없으면 입력받은 참조를 그대로 반환 (할당 제로)
        Cow::Borrowed(input)
    }
}
}

Pin과 자기 참조 타입

Pin은 특정 값이 메모리 상에서 이동하지 못하도록 고정합니다. 이는 구조체 내부의 필드가 자기 자신의 다른 필드를 가리키는 자기 참조 구조체나, .await 지점을 넘나드는 Future 객체에 필수적입니다.


드롭 순서 (Drop Order)와 ManuallyDrop

Rust의 객체 소멸 순서는 예측 가능합니다.

  • 지역 변수: 선언된 역순으로 해제됩니다.
  • 구조체 필드: 선언된 순서(위에서 아래로)대로 해제됩니다.

복잡한 리소스 관리 시 소멸 순서를 직접 제어해야 한다면 ManuallyDrop을 사용하여 컴파일러의 자동 드롭을 막고 수동으로 drop()을 호출할 수 있습니다.


📝 연습 문제: 참조 카운팅 그래프 제작 ★★ (~30분)

Rc<RefCell<Node>>를 사용하여 A → B → C → A 형태의 순환 그래프를 만들어 보세요. Weak를 사용하여 메모리 누수 없이 모든 노드가 정상적으로 드롭되는지 확인해 보세요.


📌 요약

  • 단일 소유권Box, 다중 소유권Rc/Arc를 선택하세요.
  • 내부 가변성은 컴파일 타임 대여 규칙을 런타임으로 미루는 도구입니다. 신중히 사용하세요.
  • **Cow**는 읽기 위주의 API에서 성능 최적화의 핵심입니다.
  • 드롭 순서가 중요하다면 필드 선언 순서에 유의하세요.