러스트의 주요 목표 중 하나는 변경 불가능한 데이터의 변화를 방지하고 컴파일 단계에서 데이터 경쟁을 피하는 것입니다. 일반적으로 러스트는 불변 참조로 데이터를 변경하는 것을 허용하지 않습니다. 그러나 캐싱, 지연 계산 또는 내부 상태를 참조를 통해 변경해야 하는 로직이 필요한 시스템에서는 이것이 필요할 수 있습니다. 이를 위해 interior mutability 패턴이 등장했습니다.
interior mutability가 없으면 안전한 소유권과 참조를 유지하면서 캐시, 지연 초기화 및 기타 많은 관용구를 구현하기 어렵거나 불가능합니다. 전형적인 예는 외부 세계에 불변 참조만 제공하는 함수 내에서의 캐시입니다.
러스트에는 Cell과 RefCell(및 다중 스레드를 위한 유사체)이라는 특별한 타입이 있어, 불변 참조를 통해서도 내부 값을 변경할 수 있으며, 컴파일이 아니라 실행 시 안전성을 제어합니다.
Cell<T> — 복사 가능한 원시 타입으로, 참조를 사용하지 않고도 값을 읽거나 변경할 수 있습니다. 단, Copy를 구현한 타입에만 해당됩니다.RefCell<T> — 외부에 불변 참조만 있을 때도 “즉석에서” 가변 참조를 받을 수 있도록 허용하지만, 두 개의 가변 참조를 동시에 받거나 하나의 가변 참조와 여러 개의 불변 참조를 동시에 요청하면 실행 중 패닉이 발생합니다.코드 예시:
use std::cell::RefCell; struct Foo { cache: RefCell<Option<u32>>, } impl Foo { fn get_or_compute(&self) -> u32 { if let Some(val) = *self.cache.borrow() { return val; } let computed = 42; *self.cache.borrow_mut() = Some(computed); computed } }
주요 특징:
다중 스레드 구조체에서 RefCell을 안전하게 사용할 수 있나요?
아니오, RefCell은 스레드 안전하지 않습니다. 다중 스레드 환경에서는 Mutex 또는 RwLock을 사용하세요.
Cell<T>의 내용에 대한 참조를 반환할 수 있나요?
아니오, Cell은 아무런 참조도 제공하지 않으며, 값만 복사하거나 업데이트합니다. Copy 타입만 작동하며, 나머지는 RefCell을 사용하세요.
동일한 RefCell에서 borrow_mut을 두 번 연속으로 호출하면 어떻게 되나요?
런타임에서 패닉이 발생합니다. RefCell은 활성 참조 수를 추적합니다. 이미 존재하는 참조가 있는 상태에서 두 번째로 가변 접근을 시도하면 패닉이 발생합니다.
프로젝트에서 구조체 내의 모든 변경 가능한 상태를 저장할 때 항상 RefCell을 사용하여 가변 소유권을 사용할 수 있음에도 불구하고 코드가 장황해지며 런타임에서 패닉이 발생하고 테스트하기 어려워집니다.
장점: 컴파일러의 제약을 빠르게 우회하고 원하는 동작을 얻을 수 있습니다.
단점: 실행 단계에서 오류 발생 위험이 높아지고, 애플리케이션이 충돌하며, 로직 디버깅이 어려워집니다.
RefCell은 대규모 구조에서 지연 캐시를 구현하는 데만 사용하고 나머지 코드에서는 전통적인 소유권과 참조를 유지합니다. 모든 값이 올바르게 정리되며 패닉이 없습니다.
장점: 투명한 로직과 interior mutability 사용 최소화로 코드를 안정적이고 예측 가능하게 만듭니다.
단점: 데이터 변경의 경계에 대해 주의해야 하며, 캐시 읽기는 항상 안전하고, 기록은 특정 상황에서만 이루어져야 합니다.