Rust: 스마트 포인터
개요
- 추가적인 메타데이터와 능력들도 가지고 있는 포인터
- 참조자가 데이터를 오직 빌리기만 하는 포인터
- 스마트 포인터는 그들이 가리키고 있는 데이터를 소유
Deref,DerefMut와Drop트레잇을 구현한 구조체를 이용하여 구현Deref- 인스턴스가 참조자처럼 동작하게 해줌
- 역참조 강제(deref coercion)
- Deref를 구현한 어떤 타입의 참조자를 Deref가 본래의 타입으로부터 바꿀 수 있는 타입의 참조자로 변경
- 불변 참조자에 대한 *를 오버 라이딩 가능
DerefMut- 가변 참조자에 대한 *를 오버 라이딩 가능
Drop- 인스턴스가 스코프 밖으로 벗어났을 때 실행되는 코드를 오버 라이딩 가능
- std::mem::drop 함수를 이용하여 수동 drop 가능
Box
- 데이터를 힙에 저장
- 사용 예시
- 컴파일 타임에 크기를 알 수 없는 타입(재귀적 타입 (recursive type))을 사이즈를 알아야하는 로직에 이용하고 싶을 경우
- 소유권을 옮길 때 복사가 일어나지 않음을 보장받고 싶을 경우
- 특정 트레잇을 구현한 타입이라는 점만 신경 쓰고 싶을 경우
Rc
- 참조 카운팅 (reference counting) 의 약자
- 복수 소유자를 갖는 것이 가능
- 어떤 값이 계속 사용되는지 혹은 그렇지 않은지를 알기 위해 해당 값에 대한 참조자의 갯수를 계속 추적
- 단일 스레드 시나리오 상에서만 사용 가능
- Rc::clone 함수
- 호출할 때 참조 카운트 증가
- 러스트의 관례는 a.clone() 보다 Rc::clone(&a)를 이용
- 깊은 복사 종류의 클론과 참조 카운트를 증가시키는 종류의 클론을 시각적으로 구별 가능
- Rc::strong_count 함수
- 참조 카운트 반환
RefCell
- 내부 가변성 패턴을 따르는 타입
- 내부 가변성 (interior mutability)
- 어떤 데이터에 대한 불변 참조자가 있을 때라도 데이터를 변경할 수 있게 해주는 러스트의 디자인 패턴
- 빌림 규칙에 의해 허용되지 않으므로 데이터 구조 내에서 unsafe (안전하지 않은) 코드를 사용
- 런타임에 빌림 규칙을 따를 것임을 보장할 수 있다면, 컴파일러가 이를 보장하지 못하더라도 내부 가변성 패턴을 이용하는 타입 사용 가능
- unsafe 코드는 안전한 API로 감싸져 있고, 외부 타입은 여전히 불변이므로 컴파일 통과
- 런타임에 빌림 규칙에 어긋나면 패닉 발생
- Rc
와는 다르게 단일 소유자을 지님 - 단일 스레드 시나리오 상에서만 사용 가능
- 빌림 규칙을 따르는 것을 확신하지만, 컴파일러는 이를 이해하고 보장할 수 없을 경우 유용
- Rc와의 조합을 자주 사용
Rc<RefCell<T>>- 복수 소유자를 갖으면서 값 변경이 가능
Weak
- 약한 참조(weak reference)
- 순환 참조 방지에 자주 쓰임
- Rc::clone 함수 대신 Rc::downgrade 함수를 호출
- Weak
타입의 스마트 포인터 반환 - weak_count를 1 증가
- Weak
- weak_count가 0이 아니여도 strong_count가 0이면 인스턴스 제거
- upgrade 메소드
- 참조하는 값 확인
- Option<Rc
>를 반환
- Rc::weak_count 함수
- 참조 카운트 반환
Arc
- 아토믹 참조 카운팅
- Rc의 스레드세이프 버전
예제 1
-
코드
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
fn func(s: &str) {
println!("{}", s);
}
fn main() {
println!("1.1 : {}", Box::new(1));
println!("1.2 : {}", *Box::new(1));
println!("1.3 : {}", &Box::new(1));
println!("1.4 : {}", *MyBox::new(1));
func("2.1 : a");
func(&Box::new(String::from("2.2 : b")));
func(&MyBox::new(String::from("2.3 : c")));
}
-
실행 결과
1.1 : 1
1.2 : 1
1.3 : 1
1.4 : 1
2.1 : a
2.2 : b
2.3 : c
예제 2
-
코드
#[derive(Debug)]
struct Test {
s: String,
}
impl Drop for Test {
fn drop(&mut self) {
println!("drop call - {}", self.s);
}
}
fn main() {
{
let test1 = Test {
s: String::from("1.1 : a"),
};
let test2 = Test {
s: String::from("1.2 : b"),
};
println!("1.3 : {:?}, {:?}", test1, test2);
}
{
let test1 = Test {
s: String::from("2.1 : a"),
};
let test2 = Test {
s: String::from("2.2 : b"),
};
println!("2.3 : {:?}, {:?}", test1, test2);
drop(test1);
}
}
-
실행 결과
1.3 : Test { s: "1.1 : a" }, Test { s: "1.2 : b" }
drop call - 1.2 : b
drop call - 1.1 : a
2.3 : Test { s: "2.1 : a" }, Test { s: "2.2 : b" }
drop call - 2.1 : a
drop call - 2.2 : b
예제 3
-
코드
use std::rc::Rc;
use List::{Cons, Nil};
#[derive(Debug)]
enum List {
Cons(i32, Rc<List>),
Nil,
}
fn main() {
{
let l1 = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("1.1 : {:?}, {}", l1, Rc::strong_count(&l1));
let l2 = Cons(3, Rc::clone(&l1));
println!("1.2 : {:?}, {}", l2, Rc::strong_count(&l1));
let l3 = Cons(4, Rc::clone(&l1));
println!("1.3 : {:?}, {}", l3, Rc::strong_count(&l1));
}
{
let l1 = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("2.1 : {:?}, {}", l1, Rc::strong_count(&l1));
{
let l2 = Cons(3, Rc::clone(&l1));
println!("2.2 : {:?}, {}", l2, Rc::strong_count(&l1));
}
let l3 = Cons(4, Rc::clone(&l1));
println!("2.3 : {:?}, {}", l3, Rc::strong_count(&l1));
}
}
-
실행 결과
1.1 : Cons(5, Cons(10, Nil)), 1
1.2 : Cons(3, Cons(5, Cons(10, Nil))), 2
1.3 : Cons(4, Cons(5, Cons(10, Nil))), 3
2.1 : Cons(5, Cons(10, Nil)), 1
2.2 : Cons(3, Cons(5, Cons(10, Nil))), 2
2.3 : Cons(4, Cons(5, Cons(10, Nil))), 2
예제 4
-
코드
use std::cell::RefCell;
fn func_1(v: &Vec<String>) -> usize {
v.len()
}
fn func_2(v: &mut Vec<String>, s: String) {
v.push(String::from(s));
}
fn main() {
let ref_cell = RefCell::new(vec![]);
println!("1 : {}", ref_cell.borrow().len());
ref_cell.borrow_mut().push(String::from("a"));
println!("2 : {}", ref_cell.borrow().len());
println!("3 : {}", func_1(&ref_cell.borrow()));
println!("4 : {}", func_1(&ref_cell.borrow_mut()));
func_2(&mut ref_cell.borrow_mut(), String::from("b"));
println!("5 : {}", ref_cell.borrow_mut().len());
}
-
실행 결과
1 : 0
2 : 1
3 : 1
4 : 1
5 : 2
예제 5
-
코드
use std::rc::Rc;
#[derive(Debug)]
struct Test {
s: String,
}
impl Drop for Test {
fn drop(&mut self) {
println!("drop call - {}", self.s);
}
}
fn main() {
let rc = Rc::new(Test {
s: String::from("1.1 : a"),
});
println!("1 : {:?}", rc);
let weak = Rc::downgrade(&rc);
println!("2.1 : {}", Rc::weak_count(&rc));
println!("2.2 : {:?}", weak);
match weak.upgrade() {
Some(_test) => println!("2.3 : {:?}", rc),
None => println!("2.3 None"),
}
let _weak = Rc::downgrade(&rc);
println!("2.4 : {}", Rc::weak_count(&rc));
}
-
실행 결과
1 : Test { s: "1.1 : a" }
2.1 : 1
2.2 : (Weak)
2.3 : Test { s: "1.1 : a" }
2.4 : 2
drop call - 1.1 : a