에러와 메모리 연습 문서의 문제 3 풀이 코드다.
//! 연습 p3: 폴더 트리에서 자식이 부모를 Rc로 붙잡아 생기는 누수를 Drop 횟수로 찾고,
//! Weak로 고친 풀이. 고친 뒤 경로 문자열("/home/kim/docs")도 Weak를 따라 만든다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/30_errors-memory-practice_p3.rs"
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
thread_local! {
static DROPS: Cell<u32> = const { Cell::new(0) };
}
fn drops() -> u32 {
DROPS.with(|d| d.get())
}
fn reset() {
DROPS.with(|d| d.set(0));
}
// ----- 문제의 코드: parent도 Rc -----
struct LeakyDir {
_name: String,
parent: RefCell<Option<Rc<LeakyDir>>>,
children: RefCell<Vec<Rc<LeakyDir>>>,
}
impl Drop for LeakyDir {
fn drop(&mut self) {
DROPS.with(|d| d.set(d.get() + 1));
}
}
fn leaky_mkdir(parent: &Rc<LeakyDir>, name: &str) -> Rc<LeakyDir> {
let child = Rc::new(LeakyDir {
_name: name.into(),
parent: RefCell::new(Some(Rc::clone(parent))),
children: RefCell::new(vec![]),
});
parent.children.borrow_mut().push(Rc::clone(&child));
child
}
// ----- 풀이: parent는 Weak -----
struct Dir {
name: String,
parent: RefCell<Weak<Dir>>,
children: RefCell<Vec<Rc<Dir>>>,
}
impl Drop for Dir {
fn drop(&mut self) {
DROPS.with(|d| d.set(d.get() + 1));
}
}
fn root() -> Rc<Dir> {
Rc::new(Dir {
name: String::new(),
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
})
}
fn mkdir(parent: &Rc<Dir>, name: &str) -> Rc<Dir> {
let child = Rc::new(Dir {
name: name.into(),
parent: RefCell::new(Rc::downgrade(parent)),
children: RefCell::new(vec![]),
});
parent.children.borrow_mut().push(Rc::clone(&child));
child
}
fn path(d: &Rc<Dir>) -> String {
let mut parts = vec![d.name.clone()];
let mut cur = d.parent.borrow().upgrade();
while let Some(p) = cur {
parts.push(p.name.clone());
cur = p.parent.borrow().upgrade();
}
parts.reverse();
let joined = parts.join("/");
if joined.is_empty() {
"/".into()
} else {
joined
}
}
fn main() {
// 문제: 디렉터리 4개(/, home, kim, docs)를 만들고 모두 버린다
reset();
{
let r = Rc::new(LeakyDir {
_name: String::new(),
parent: RefCell::new(None),
children: RefCell::new(vec![]),
});
let home = leaky_mkdir(&r, "home");
let kim = leaky_mkdir(&home, "kim");
let docs = leaky_mkdir(&kim, "docs");
assert!(docs.parent.borrow().is_some());
println!(
"leaky: strong_count(/)={}, (home)={}, (kim)={}",
Rc::strong_count(&r),
Rc::strong_count(&home),
Rc::strong_count(&kim)
);
assert_eq!(Rc::strong_count(&r), 2);
assert_eq!(Rc::strong_count(&home), 3);
}
println!("leaky: Drop {}번 (4개 중)", drops());
assert_eq!(drops(), 0);
// 풀이
reset();
{
let r = root();
let home = mkdir(&r, "home");
let kim = mkdir(&home, "kim");
let docs = mkdir(&kim, "docs");
assert_eq!(path(&docs), "/home/kim/docs");
assert_eq!(path(&r), "/");
assert_eq!(Rc::strong_count(&r), 1);
assert_eq!(Rc::weak_count(&r), 1);
assert_eq!(Rc::strong_count(&home), 2);
}
println!("weak: Drop {}번 (4개 중)", drops());
assert_eq!(drops(), 4);
// 변형: 루트만 버리고 docs를 쥐고 있으면?
reset();
let docs = {
let r = root();
let home = mkdir(&r, "home");
let kim = mkdir(&home, "kim");
mkdir(&kim, "docs")
};
assert_eq!(drops(), 3); // 위쪽 세 개는 아무도 강하게 쥐지 않아 해제된다
assert_eq!(path(&docs), "docs");
println!("변형: docs만 남기면 Drop 3번, path = {:?}", path(&docs));
println!("ALL CHECKS PASSED");
}