이 시리즈는 Rust 공식문서를 통해 공부한 흔적임을 밝힙니다.
Rust는 다른 언어에 비해 메모리 안전성을 잘 보장해준다.
그렇다고 해서 Rust로 작성하면 별 짓을 다 해도 메모리 누수가 발생하지 않는 것은 아니다.
메모리 누수가 발생하지 않는 코드를 작성하도록 주의할 필요가 있다.
순환 참조란, 서로가 서로를 참조하여 참조 구조가 cycle을 이루는 것을 의미한다.
예를 들어, 피터가 "내 옆에 페리스 있어!" 하고 페리스가 "내 옆에 피터 있어!" 한다고 하자.
이 때 아무도 언급하지 않는 자를 한 명씩 데려갈 수 있다고 하자.
피터를 데려가려고 하면 페리스가 "피터 데려갈거면 내 말부터 정정해줘!" 하고 막고
페리스를 데려가려고 하면 피터가 "페리스 데려갈거면 내 말부터 정정해줘!" 하고 막는다.
결국 우리는 피터와 페리스를 데려갈 수 없고 그들은 계속 메모리에 상주하게 된다.
순환 참조를 방지하기 위해 Rc<T>
대신 Weak<T>
를 사용할 수 있다.
우리가 Rc<T>
의 참조 카운터 값을 확인할 때 살펴봤듯이
Rc<T>
는 strong_count
값을 증가시키고 이를 추적한다.
그런데 Rc::clone
메서드 대신 Rc::downgrade
메서드를 사용하여
Weak<T>
를 반환받을 수 있다.
이 녀석은 weak_count
값을 증가시키고 추적할 수 있다.
weak_count
는 0이 아니더라도 strong_count
만 0이 되면 Rc<T>
를 해제 가능하다.
Weak<T>
는 그것이 값을 가지고 있어도 Rc<T>
가 해제되어버릴 수 있기 때문에
사용할 때마다 유효성 검사가 필요하다.
Weak<T>
의 메서드 upgrade
를 통해 이러한 유효성 검사를 할 수 있으며
그 반환값 Option<Rc<T>>
는 해제되었다면 None
을
해제되지 않았다면 Some<Rc<T>>
을 열거값으로 가진다.
이걸 사용하는 예제로 트리 구조를 만들어보자.
트리의 연산까지는 구현하지 않을 것이고 단지 그 구조만 작성할 것이다.
이 때, 트리의 모든 노드는 그것의 자식 노드 참조 벡터와 부모 노드 참조를 가진다.
따라서 우리는 부모 노드가 자식 노드를 소유하고
자식 노드는 부모 노드를 약한 참조로 가지고 있도록 작성할 것이다.
peter@hp-laptop:~/rust-practice/chapter15$ cargo new tree
Created binary (application) `tree` package
peter@hp-laptop:~/rust-practice/chapter15$ cd tree/
peter@hp-laptop:~/rust-practice/chapter15/tree$ vi src/main.rs
src/main.rs
use std::rc::{Rc, Weak}; use std::cell::RefCell; #[derive(Debug)] struct Node { value: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>>, } fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!("leaf parent: {:?}", leaf.parent.borrow().upgrade()); let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!("leaf parent: {:?}", leaf.parent.borrow().upgrade()); }
peter@hp-laptop:~/rust-practice/chapter15/tree$ cargo run
Compiling tree v0.1.0 (/home/peter/rust-practice/chapter15/tree)
Finished dev [unoptimized + debuginfo] target(s) in 0.23s
Running `target/debug/tree`
leaf parent: None
leaf parent: Some(Node { value: 5, parent: RefCell { value: (Weak) }, children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) }, children: RefCell { value: [] } }] } })
peter@hp-laptop:~/rust-practice/chapter15/tree$
여기서 적절한 위치에 Rc::strong_count(&leaf);
또는 Rc::weak_count(&leaf);
를 넣어
그것의 참조 개수를 확인해볼 수 있다.
이 포스트의 내용은 공식문서의 15장 6절 Reference Cycles Can Leak Memory에 해당합니다.