[Rust] 스마트 포인터
Updated:
개요
- 추가적인 메타데이터와 능력들도 가지고 있는 포인터
- 참조자가 데이터를 오직 빌리기만 하는 포인터
- 스마트 포인터는 그들이 가리키고 있는 데이터를 소유
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
-