[Rust] 스마트 포인터
1. 스마트 포인터
C에서와 마찬가지로 포인터는 메모리의 주소를 저장하고 있습니다.
스마트 포인터는 다른 포인터와 마찬가지로 주소를 저장하고 있습니다.
그런데 스마트 포인터가 가리키는 메모리 영역은 stack이 아니라 heap입니다.
즉 스마트 포인터의 헤더는 stack에 있고 실내용물은 heap에 있습니다.
스마트 포인터는 왜 필요할까요?
String을 통해 생각해 봅시다. 우리가 이미 만나본 데이터 타입인 String은 대표적인 스마트 포인터입니다.
이 말은 String의 헤더는 stack에 있고 실내용물은 heap에 있다는 의미입니다.
stack에 있는 String의 헤더에는 heap 영역에 있는 실제 값의 주소가 담겨 있습니다.
값이 stack에 있는 것과 heap에 있는 것은 뭐가 다를까요?
Stack에 존재하는 데이터 타입들은 타입 별로 그 크기가 엄격하게 정해져 있습니다.
그래서 규칙적이고 예측 가능하게 순차적으로 저장될 수 있습니다.
예를 들어 우리가 i8 타입의 변수를 만들었다면 Rust는 stack 메모리에 8비트만큼의 공간을 만들어 그 변수의 값을 저장합니다. 그리고 이 저장 크기는 이후에 변할 가능성이 없습니다.
그런데 String의 경우는 실내용물이 heap에 있기 때문에 그 크기가 변할 수 있습니다. 실례를 보겠습니다.
let mut s = String::from("Welcome");
s.push_str(" to Rust");
println!("{}", s);
// 출력
Welcome to Rust
문자열 s에 push_str 메소드로 문자열을 추가해서 길이를 늘였습니다.
데이터 용량을 바꿀 수 있는 것은 Vec 데이터 타입에서도 확인할 수 있습니다.
Vec도 String처럼 스마트 포인터입니다.
let mut v = vec![1];
println!("{}", v.len()); // Vec의 길이 출력
v.push(2);
println!("{}", v.len()); // Vec의 길이 출력
// 출력
1
2
이런 편리함 뿐만 아니라 자원의 효율적인 사용을 위해서도 스마트 포인터는 필요합니다.
예를 들어서 우리가 거대한 이미지 데이터를 다루는 상황에서 만약 이 이미지 데이터가 stack에 존재한다고 가정해 봅시다.
let x = image_data;
let y = x;
let z = y;
image_data로 표시한 것이 거대한 이미지 데이터라고 가정합시다.
그리고 위와 같은 작업을 했다면 우리가 매번 재할당 할 때마다 거대한 이미지 데이터는 복사되어서 저장될 것입니다.
위 예에서도 똑같은 내용물의 이미지 데이터가 세 번이나 중복되어서 메모리를 차지하게 되었습니다.
이미지 데이터의 실내용이 heap에 있다면 복사하지 않고 관리할 수 있습니다.
이런 예는 이미 String을 통해 확인해 본 적이 있습니다. String을 재할당할 때 데이터가 자동으로 복사되지 않아 소유권 이동이 일어납니다. 데이터가 복사되게 강제하려면 Copy 트레이트를 구현해야 했습니다.
스마트 포인터의 헤더는 stack에 있기 때문에 스코프에 따른 소유권 규칙이 적용 되어서 효과적으로 변수를 관리할 수 있습니다.
다양한 스마트 포인터가 있지만 이 장에서 설명한 스마트 포인터의 일반적 특성은 공통적으로 가지고 있습니다.
즉 헤더는 stack에 있고 실 데이터는 heap에 있습니다.
다음 장부터 알아볼 각각의 스마트 포인터는 저마다의 특징이 있습니다.
서로 어떤 차이가 있는지에 초점을 맞추면 공부하기 수월합니다.