안녕하세요.
오늘은 스마트 포인터에 대해 알아보고,
그 중 Box<T>, Rc<T>에 대해 살펴보도록 하겠습니다.
📌 스마트 포인터
스마트 포인터는 일반적인 포인터와 달리, 메모리 해제 및 관리 기능을 제공하는 포인터 입니다.
- 소유권과 수명을 따르며,
- Drop 트레잇을 구현하여 자동으로 메모리 정리가 됩니다
Rust에서 자주 사용하는 스마트 포인터 중 Box<T>와 Rc<T>에 대해 알아보겠습니다.
📌 Box<T>
✅ 기본 개념
힙 할당 스마트 포인터
fn main() {
let x = Box::new(10); // 10을 힙에 저장
println!("x = {}", x); // 10
}
- Box<T>를 사용하면 값(10)은 힙에 저장됩니다.
- x는 힙을 가리키는 포인터 역할을 합니다.
- Box<T>는 기본적으로 Deref와 Drop 트레잇을 구현하여, 자동으로 참조 해제 및 메모리 해제를 수행합니다.
Deref와 Drop 트레잇 | Notion
스마트 포인터를 더 편리하게 사용하고, 메모리 관리를 자동화하기 위해 Deref와 Drop 이라는 두 트레잇을 제공함
mixolydian-tadpole-478.notion.site
✅ When?
Box<T>를 언제 사용하는지 알아봅시다.
Case1) 크기가 큰 데이터를 힙에 저장하여, 스택 메모리를 절약할 때
Rust는 기본적으로 모든 데이터가 스택에 저장됩니다.
Box<T>를 사용하면 큰 데이터를 힙에 저장하고, 스택에는 작은 크기의 포인터만 저장할 수 있습니다.
- 32비트 또는 64비트
struct BigData {
data: [u8; 1024], // 1KB 크기의 배열
}
fn main() {
let _big_data = Box::new(BigData { data: [0; 1024] }); // 힙에 저장
}
- 스택에는 Box<T> 포인터만 저장되고,
- 실제 데이터는 힙에 저장됩니다.
Case2) 컴파일 타임에 크기를 알 수 없는 타입을 저장할 때
Rust는 모든 변수의 크기를 컴파일 타임에 알아야 합니다.
런타임에 크기가 결정되는 경우도 있는데, Box<T>를 사용해 해결할 수 있습니다.
#[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) // Cons(1, Cons(2, Nil))
}
📌 Rc<T>
✅ 기본 개념
참조 카운팅 스마트 포인터
- 하나의 데이터를 여러 개의 소유자가 공유할 수 있도록 해줍니다.
- 데이터가 더 이상 사용되지 않을 때 자동으로 메모리를 해제합니다.
✅ When?
여러 개의 소유자가 동일한 값을 공유해야 할 때 사용합니다.
아래 코드는 컴파일 에러가 납니다.
struct Node {
value: i32,
next: Option<Box<Node>>, // Box<Node>는 한 개의 소유자만 가능!
}
fn main() {
let node1 = Node {
value: 10,
next: None,
};
let node2 = Node {
value: 20,
next: Some(Box::new(node1)), // ❌ 여기서 node1의 소유권이 이동됨
};
println!("{}", node1.value); // ❌ 에러 발생! (node1의 소유권이 이동되었기 때문)
}
- node1은 node2에게 소유권이 넘어가서 더 이상 사용할 수 없습니다.
- 여러 개의 노드가 같은 데이터를 공유하고 싶을 때 → Rc<T>를 사용할 수 있습니다.
use std::rc::Rc;
struct Node {
value: i32,
next: Option<Rc<Node>>, // 여러 개의 소유자가 가능하도록 Rc<T> 사용
}
fn main() {
let node1 = Rc::new(Node {
value: 10,
next: None,
});
let node2 = Node {
value: 20,
next: Some(Rc::clone(&node1)), // Rc<T>의 참조 카운트를 증가시킴
};
let node3 = Node {
value: 30,
next: Some(Rc::clone(&node1)), // 또 다른 노드도 node1을 공유 가능
};
println!("node1을 가리키는 참조 개수: {}", Rc::strong_count(&node1)); // 출력: 3
println!("{}", node1.value); // 10
println!("{}", node2.next.as_ref().unwrap().value); // 10
println!("{}", node3.next.as_ref().unwrap().value); // 10
}
- Rc::new(value) → Rc 인스턴스를 생성합니다.
- Rc::clone(&rc_value) → 실제 데이터를 복사하는 것이 아니라, 참조 카운트를 증가시킵니다.
- Rc::string_count(&rc_value) → 현재 Rc가 몇 개의 소유자를 가지고 있는지 확인합니다.
- Rc::weak_count(&rc_value) → Weak (약한 참조) 개수를 확인할 때 사용합니다.
✅ 한계
1) 가변성을 허용하지 않는다
- 내부 값을 변경하고 싶다면 RefCell<T>와 함께 사용해야 합니다.
2) 멀티스레드 환경에서 사용이 불가능하다
- 멀티스레드 환경에서는 Arc<T>를 사용해야 합니다.
'Rust' 카테고리의 다른 글
[Rust] 에러 처리 (0) | 2025.03.30 |
---|---|
[Rust] 스마트 포인터 (2) - RefCell<T>, Arc<T>, Mutex<T> (0) | 2025.03.24 |
[Rust] 제네릭(Generic), 트레잇(Trait), 동적 디스패처(dynamic dispatch) (2) | 2025.03.11 |
[Rust] 소유권, 참조와 빌림, 슬라이스 (0) | 2025.03.03 |
[Rust] 기초 문법 (2) | 2025.02.28 |