프로그래밍시스템 프로그래밍

클래스 파괴 시 자원을 수동으로 관리하는 방법에 대해 Rust에서 RAII가 어떻게 구현되었으며, 그것이 가비지 수집기와 어떻게 다른가요?

Hintsage AI 어시스턴트로 면접 통과

답변.

질문의 역사

RAII (Resource Acquisition Is Initialization) — C++에서 전해지는 관용구로, 자원의 생명 주기가 스택에서 객체의 생명 주기와 엄격히 연결됩니다. Rust에서는 이 개념이 자원의 소유 및 해제 시스템의 기초가 되어 전통적인 가비지 수집기 (GC) 없이도 작동할 수 있게 됩니다.

문제

많은 언어들이 가비지 수집기를 통해 메모리와 자원을 관리하며, 해당 수집기는 주기적으로 필요 없는 객체를 "정리합니다". 이 전략은 지연을 증가시키고 외부 자원(파일, 소켓 등)의 즉각적인 해제를 보장하지 않습니다. 저수준 및 시스템 프로그래밍에서는 이러한 상황이 허용되지 않습니다: 자원 관리가 정확하고 결정적이어야 합니다.

해결책

Rust에서는 각 객체가 자신의 자원을 소유하며, 그것을 엄격히 파괴 위치 (out of scope)에서 해제합니다. Drop 호출을 통해 (소멸자와 유사) 자원은 즉시 해제되며, 모든 암묵적 해제 오류는 최소화됩니다. Rust의 타입 시스템과 소유권은 메모리 유출과 이중 해제를 컴파일 단계에서 거의 방지합니다.

코드 예:

struct FileWrapper { file: std::fs::File, } impl Drop for FileWrapper { fn drop(&mut self) { println!("FileWrapper가 파일을 닫습니다! (scope exit)"); } } fn main() { let _fw = FileWrapper { file: std::fs::File::create("test.txt").unwrap() }; // main에서 나올 때 drop이 보장적으로 호출됩니다. }

주요 특징:

  • RAII는 영역을 벗어날 때 자원을 동기적으로 해제하는 것을 보장합니다.
  • C 또는 C++와 같이 해제를 수동으로 호출할 필요가 없으며, GC에서 오는 "놀라움"도 없습니다.
  • 메모리뿐만 아니라 모든 자원(락, 디스크립터, 파일 등)에서 작동합니다.

트릭 질문들.

이전의 값이 이동되었을 때 Drop이 호출됩니까?

아니요, 값이 이동된 후 Drop은 새로운 소유자에게만 호출되며, 이전 객체는 "비어 있는" 것으로 간주되어 Drop이 호출되지 않습니다.

let file1 = FileWrapper {...}; let file2 = file1; // file1 이동 // Drop은 file2에 대해 한 번만 호출됩니다.

panic! 또는 unwrap()이 영역 안에서 Drop 호출을 방해할 수 있습니까?

아니요, 패닉 또는 오류로의 종료는 소멸자 호출을 취소하지 않으며 — Drop은 영역을 벗어난 모든 객체에 대해 반드시 호출됩니다.

참조가 객체를 소유하는 경우, 참조의 생애가 종료될 때 drop이 호출됩니까?

아니요, drop은 객체의 소유자에게만 호출되며, 참조는 소유권을 행사하지 않습니다. Heap 자원을 위한 스마트 포인터가 필요합니다.

일반적인 오류 및 안티패턴

  • 참조 또는 비소유자에 대해 Drop이 호출될 것으로 기대하면 — 자원 누수가 발생합니다.
  • Rc/Arc에 자원을 이동시키면(shared-ownership을 이해하지 않고) — 적어도 하나의 Rc가 남아있을 때 객체는 해제되지 않습니다.

실생활 사례

부정적인 케이스

개발자가 파일 디스크립터를 링크로 쓰기 함수에 전달했습니다. 프로그램 작업이 완료된 후, 파일의 잠금이 남아 있었으며, Drop이 호출되지 않았기 때문입니다 (소유자가 없고 참조가 사용됨).

장점:

  • 프로토타입 구현이 간단합니다.

단점:

  • Lock-파일이 해제되지 않았습니다.
  • 디스크립터 누수 발생.

긍정적인 케이스

자원의 소유는 항상 Drop을 구현한 구조체를 통해 전달되었습니다. 모든 열린 파일, 연결 또는 locks는 scope 종료 시 또는 panic 시 자동으로 해제됩니다. 복잡한 프로젝트에서의 안전하고 간단한 자원 관리에 대한 기회를 제공합니다.

장점:

  • 누수가 없습니다.
  • "죽어있는 디스크립터"가 없습니다.
  • 최소한의 보일러플레이트.

단점:

  • move semantics 및 소유권 규칙을 기억해야 합니다.