[러스트/Rust] 스마트 포인터(Smart Pointer), 박스(Box<T>) 개념 정리

2022. 5. 30. 18:36Programming Language/Rust

반응형

■ 스마트 포인터

스마트 포인터는 메모리의 주소 값을 갖는 변수로 알고 있는 포인터처럼 동작합니다. 또한 빌리기만 하는 참조자와 달리 가리키고 있는 데이터를 소유하며, 추가적인 메타데이터와 기능들을 가지고 있는 데이터 구조입니다.

 

스마트 포인터는 기본적으로 구조체(Struct) 형으로 정의하며, Deref(std::ops::Deref) 와 Drop(std::ops::Drop) 트레잇을 구현해야 합니다. Deref 트레잇은 스마트 포인터의 인스턴스가 참조자처럼 동작하도록하고, Drop 트레잇은 스마트 포인터의 인스턴스가 스코프 밖으로 벗어났을 때 스마트 포인터의 메모리와 소유권을 정리하고 원하는 로직을 커스터마이징 할 수 있도록 합니다.

 

자세한 사용 예시는 Box<T> 와 동일한 스마트 포인터를 만들어보면서 설명하겠습니다.


◆ Box<T>타입과 유사한 커스텀 스마트 포인터 활용 예시

use std::ops::Deref;
use std::ops::Drop;

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 {
       println!("Smart Pointer deref call !!");
       &self.0
    }
}

impl<T>Drop for MyBox<T>{
    fn drop(&mut self) {
       println!("Smart Pointer drop !!")
    }
}

#[cfg(test)]
mod tests {
    use crate::MyBox;

    #[test]
    fn my_box_test() {
        let my_box = MyBox::new(10);
        assert_eq!(10, *my_box); // deref 메소드가 호출됨
    }// my_box_test 함수가 종료되면서 drop 메소드가 호출됨
}

■ Box<T>

러스트는 스택에 데이터를 저장할 때 해당 데이터의 사이즈를 알 수 없으면, 컴파일 타임에서 에러로 표시합니다. 데이터의 사이즈를 확인할 수 없는 경우는 크게 트레잇 오브젝트를 활용할 때나 재귀적 타입을 활용할 때를 예시로 들 수 있습니다.

이런 경우 박스를 활용할 수 있는데, 박스는 컴파일 타임에서 사이즈를 알 수 없는 타입의 데이터를 힙 공간에 저장하고,  포인터를 스택에 저장함으로써 해당 데이터를 사용할 수 있도록 합니다.

또한 방대한 양의 데이터의 소유권을 옮길 때 러스트는 스택에 데이터를 복사 하는데 이는 메모리상에 부하를 야기하고, 성능을 저하시킬 수 있습니다. 그래서 박스는 방대한 양의 데이터를 힙 공간에 저장하고 스택에서 포인터를 저장함으로써 소유권이 옮겨졌을 때 스택안의 포인터만 복사되어 성능을 높일 수 있습니다.


◆ 사이즈를 알 수 없는 재귀적 타입의 Box<T> 활용 예시

// 재귀적 타입인 List 열거형을 Box로 정의하지 않으면, 무한 재귀로 데이터의
// 사이즈를 알 수 없기 때문에 컴파일 타임에서 오류를 표시한다.
#[derive(Debug)]
enum List{
	Cons(i32, Box<List>),
	Nil,
}

fn main() {
    let list = List::Cons(1, 
		Box::new(List::Cons(2,
			Box::new(List::Nil))));
    println!("list : {:?}", list);

    // 출력 결과
    // list : Cons(1, Cons(2, Nil))
}
반응형