문제의 역사:
Rust에서는 엄격한 소유 및 객체 생애 주기 규칙 덕분에 가비지 컬렉터 없이 자원 관리를 할 수 있게 되었습니다. 초기부터 언어 개발 과정에서 파일 디스크립터, 소켓, 외부 라이브러리 메모리와 같은 자원의 자동 해제를 위해 Drop trait가 도입되었습니다. 이는 객체의 생명 주기가 끝날 때 자원을 운영 체제에 반환하거나 메모리를 해제하는 주요 반응 방법입니다.
문제:
Rust의 일반 타입은 자체 자원을 자동으로 정리하지만, 구조체가 수동 해제가 필요한 자원(예: 열려 있는 파일이나 안전하지 않은 포인터)을 저장할 경우, 개발자의 원하지 않거나 잊어버림으로 인해 자원이 누수되거나 경쟁 상태가 발생할 수 있습니다. 만약 Drop이 잘못 구현되면(예: 중복 메모리 해제를 고려하지 않은 경우) 런타임 오류를 초래할 수 있습니다.
해결책:
Drop trait는 값이 소멸될 때 자동으로 호출되는 특별한 메서드 drop(&mut self)를 정의할 수 있게 해줍니다. 이는 객체가 가시성을 잃을 때 자원 해제를 위해 적합합니다. 자원(예: 파일을 닫는 것)을 해제하는 것은 오직 이곳에서만 해야 함을 기억하는 것이 중요합니다.
코드 예시:
struct RawFile { handle: *mut libc::FILE, } impl Drop for RawFile { fn drop(&mut self) { if !self.handle.is_null() { unsafe { libc::fclose(self.handle); } } } }
주요 특징:
자원을 빨리 해제하기 위해 객체에 대해 drop을 명시적으로 호출할 수 있습니까?
아니요, drop 메서드를 직접 호출하는 것은 금지되어 있으며, 오직 std::mem::drop(obj) 함수를 통해 객체를 소유하고 drop을 자동으로 호출해야 합니다. drop()을 직접 호출하면 컴파일되지 않습니다.
코드 예시:
fn main() { let f = File::open("foo.txt").unwrap(); // drop(&mut f); // 컴파일 오류! std::mem::drop(f); // 올바른 방법: 파일 닫기 }
Drop이 있는 구조체가 memcpy나 move에 걸리면 어떻게 되나요? 소멸자가 두 번 호출되지 않나요?
아니요, 소멸자는 객체의 전체 생애 주기 동안 정확히 한 번만 호출되며, 컴파일러가 이를 감시합니다. 하지만 "원시" 바이트 복사 또는 mem::forget을 사용할 경우 drop이 전혀 호출되지 않을 수 있어 위험합니다.
하나의 타입에 대해 Drop과 Copy를 동시에 구현할 수 있습니까?
아니요, 컴파일러는 이 조합을 금지합니다: Drop을 구현하는 타입은 Copy가 될 수 없으며, 이는 소멸자가 단 한 번 호출되도록 보장하고 자원 중복 해제를 방지하기 위해서입니다.
프로그래머가 열린 파일을 다루기 위한 간단한 래퍼를 작성했지만 Drop을 구현하여 객체가 삭제될 때 디스크립터를 닫는 것을 잊어버렸습니다.
장점:
단점:
개발자가 파일 디스크립터 래퍼에 대한 Drop을 구현하여 drop에서 파일을 명시적으로 닫았습니다. 이제 구조체의 어떤 변수라도 함수에서 나오거나 panic 발생 시 자원이 보장되게 해제됩니다.
장점:
단점: