Rust의 스마트 포인터

Rust의 스마트 포인터

날짜
생성자
ShalomShalom
카테고리
rust
작성일
2023년 05월 15일
태그

스마트 포인터

  • 값을 힙에 할당하기 위한 Box<T>
  • 복수개의 소유권을 가능하게 하는 참조 카운팅 타입인 Rc<T>
  • 빌림 규칙을 컴파일 타임 대신 런타임에 강제하는 타입인, RefCell<T>를 통해 접근 가능한 Ref<T>와 RefMut<T>

Box<T>

힙 영역을 가르키는 포인터

Needs

  • 컴파일 타임에 크기를 알 수 없는 타입을 갖고 있고, 정확한 사이즈를 알 필요가 있는 맥락 안에서 해당 타입의 값을 이용하고 싶을 때
  • 커다란 데이터를 가지고 있고 소유권을 옮기고 싶지만 그렇게 했을 때 데이터가 복사되지 않을 것이라고 보장하기를 원할 때 → 보통 포인터를 사용하는 이유
  • 어떤 값을 소유하고 이 값의 구체화된 타입을 알고 있기보다는 특정 트레잇을 구현한 타입이라는 점만 신경 쓰고 싶을 때

정확한 사이즈

재귀적인 생성으로 인해 스택에 크기를 단정지을 수가 없다.
enum List { Cons(i32, List), Nil, } fn main() { let list = Cons(1, Cons(2, Cons(3, Nil)));//Err!! }
  • c++에서 스택 크기 설정
 
enum List { Cons(i32, Box<List>),//use box Nil, } use List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));//inner enum values are in heap }
따라서 동적으로 크기가 지정 될 여지가 있을 경우, Box로 heap 영역에 생성하는 것이 옳다.

Deref 트레잇

fn main() { let x = 5; let y = &x; let z = *y;//Dereference println!("x: {}", x); //x: 5 println!("y: {}", y); //y: 5 println!("z: {}", z); //z: 5 assert_eq!(5, x); assert_eq!(5, y);//no implementation for `{integer} == &{integer}` assert_eq!(5, z); }
참조자는 포인터이다. 따라서 assert_eq!를 하면 주소 값이 나오기 때문에 에러가 난다.
deref( * )를 통해 값을 불러와야한다.
fn main() { let x = 5; let y = Box::new(x); assert_eq!(5, x); assert_eq!(5, *y); }
Box도 하나의 포인터이다.

역참조 강제

fn main() { let x = 5; let y = &x; println!("x: {}", x); //x: 5 println!("y: {}", y); //y: x의 주소값? or 5? assert_eq!(5, x); assert_eq!(5, y);//no implementation for `{integer} == &{integer}` }
<aside> 💡 역참조 강제(deref coercion) 는 러스트가 함수 및 메소드의 인자에 수행하는 편의성 기능입니다. 역참조 강제는 Deref를 구현한 어떤 타입의 참조자를 Deref가 본래의 타입으로부터 바꿀 수 있는 타입의 참조자로 바꿔줍니다. 역참조 강제는 우리가 특정 타입의 값에 대한 참조자를 함수 혹은 메소드의 인자로 넘기는 중 정의된 파라미터 타입에는 맞지 않을 때 자동적으로 발생합니다. 일련의 deref 메소드 호출은 우리가 제공한 타입을 파라미터가 요구하는 타입으로 변경해 줍니다. </aside>

Drop(소멸자 정의)

struct CustomSmartPointer { data: String, } //Drop trait을 이용하여 소멸자를 정의한다. impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } fn main() { let c = CustomSmartPointer { data: String::from("my stuff") }; let d = CustomSmartPointer { data: String::from("other stuff") }; println!("CustomSmartPointers created."); } /*--------------------출력-----------------------------*/ // CustomSmartPointers created. // Dropping CustomSmartPointer with data `other stuff`! 소멸자와 같은 역할을 함 // Dropping CustomSmartPointer with data `my stuff`! 스택 영역이므로 Last in First out

강제적 소멸

fn main() { let c = CustomSmartPointer { data: String::from("some data") }; println!("CustomSmartPointer created."); c.drop(); //Err!! println!("CustomSmartPointer dropped before the end of main."); }
drop을 발생시키면 메모리 해제가 일어난다!
따라서, 해당 스코프를 벗어나면 이중 해제가 일어나기 때문에 Err가 발생한다.
따라서 drop(c) 와 같은 함수를 사용하여야 한다.
fn main() { let c = CustomSmartPointer { data: String::from("some data") }; println!("CustomSmartPointer created."); drop(c);//c의 drop을 호출! println!("CustomSmartPointer dropped before the end of main."); }//c의 drop이 발생되지 않음 /*--------------------출력-------------------------*/ // CustomSmartPointer created. // Dropping CustomSmartPointer with data `some data`! // CustomSmartPointer dropped before the end of main.

Rc (Reference Count)

CBase::CBase() : m_dwRefCnt(0) { } _ulong CBase::AddRef() { return ++m_dwRefCnt; } _ulong CBase::Release() { if (0 == m_dwRefCnt) { Free(); delete this; return 0; } else return m_dwRefCnt--; }
  • 참조할 때 마다 레퍼런스 카운트를 증가를 시킨다.
  • 참조를 해제할 때 마다 레퍼런스 카운트를 감소시킨다.
  • 레퍼런스 카운트가 0이되면 자신을 삭제시킨다.
fn main() { let a = Rc::new(10);//ref:1 println!("count after creating a = {}", Rc::strong_count(&a));//1 let b = Rc::clone(&a);//ref ++ println!("count after creating b = {}", Rc::strong_count(&a));//2 { let c = a.clone();//ref++ println!("count after creating c = {}", Rc::strong_count(&a));//3 }//ref-- println!("count after c goes out of scope = {}", Rc::strong_count(&a));//2 }

RefCell

불변 참조를 가변 참조로
fn main() { let mut a = 10; let b = &a; let c = &mut a;//Err! }
fn main() { let a = Rc::new(10); let b = a.clone(); *b = 4;//Err }
fn main() { let a = Rc::new(RefCell::new(10)); let b = a.clone(); *b.borrow_mut() = 4; }
 
💡
RefCell<T> 타입은 여러분의 코드가 빌림 규칙을 따르는 것을 여러분이 확신하지만, 컴파일러는 이를 이해하고 보장할 수 없을 경우 유용합니다.
💡
use std::borrow::BorrowMut 가 있는지 반드시 확인합시다! let mut b=10; let c = b.borrow_mut();

댓글

guest