Updated:

4 minute read

개요

  • 추가적인 메타데이터와 능력들도 가지고 있는 포인터
  • 참조자가 데이터를 오직 빌리기만 하는 포인터
  • 스마트 포인터는 그들이 가리키고 있는 데이터를 소유
  • Deref, DerefMutDrop 트레잇을 구현한 구조체를 이용하여 구현
    • 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_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