Smart Pointers

Smart Pointers

포인터는 메모리 안의 어떤 주소를 담고 있는 변수를 뜻하는 일반적인 개념이에요. 그 주소는 다른 데이터를 가리키거나 "포인트" 하죠. Rust에서 가장 흔한 포인터가 바로 4장에서 배운 레퍼런스인데요, & 기호로 표시하고 가리키는 값을 빌려와요. 레퍼런스는 단지 데이터를 가리킬 뿐이라 특별한 능력이 없고 그만큼 오버헤드도 없어요.

반면 스마트 포인터는 포인터처럼 동작하면서도 추가적인 메타데이터와 능력을 가진 데이터 구조예요. 스마트 포인터 개념은 Rust만의 것이 아니에요. C++에서 시작됐고 다른 언어에도 존재하죠. Rust 표준 라이브러리에는 레퍼런스가 제공하는 것 이상의 기능을 담은 다양한 스마트 포인터가 정의되어 있어요. 이 일반적인 개념을 살펴보기 위해 스마트 포인터의 몇 가지 예시를 볼 텐데, 그중에는 레퍼런스 카운팅 스마트 포인터 타입도 있어요. 이 포인터는 소유자 수를 계속 추적해서 데이터에 여러 소유자가 있도록 해 주고, 소유자가 아무도 남지 않으면 데이터를 정리해 줘요.

Rust에는 소유권과 빌림이라는 개념이 있어서 레퍼런스와 스마트 포인터 사이에 추가적인 차이도 있어요. 레퍼런스는 데이터를 빌리기만 하지만, 많은 경우 스마트 포인터는 가리키는 데이터를 소유해요.

스마트 포인터는 보통 struct로 구현해요. 일반 struct와 달리 스마트 포인터는 Deref 트레이트와 Drop 트레이트를 구현하죠. Deref 트레이트는 스마트 포인터 struct의 인스턴스가 레퍼런스처럼 동작하게 해 줘서, 코드를 레퍼런스로 써도 스마트 포인터로 써도 동작하도록 작성할 수 있어요. Drop 트레이트는 스마트 포인터 인스턴스가 스코프를 벗어날 때 실행되는 코드를 직접 정의할 수 있게 해 줘요. 이번 장에서는 이 두 트레이트를 모두 다루고, 왜 스마트 포인터에 중요한지 보여드릴게요.

스마트 포인터 패턴은 Rust에서 자주 쓰이는 일반적인 디자인 패턴이라, 이번 장에서 모든 기존 스마트 포인터를 다루지는 않아요. 많은 라이브러리가 각자의 스마트 포인터를 가지고 있고, 여러분이 직접 만들 수도 있죠. 표준 라이브러리에서 가장 흔한 스마트 포인터들을 다룰게요:

  • Box<T> — 값을 힙에 할당할 때 써요.
  • Rc<T> — 여러 소유권을 가능하게 하는 레퍼런스 카운팅 타입이에요.
  • Ref<T>RefMut<T>RefCell<T>을 통해 접근하는데, 빌림 규칙을 컴파일 타임 대신 런타임에 강제하는 타입이에요.

여기에 더해, 불변 타입이 내부 값을 변경하는 API를 노출하는 내부 가변성(interior mutability) 패턴도 다룰게요. 레퍼런스 사이클이 메모리를 어떻게 누수시킬 수 있는지, 또 어떻게 막는지도 함께 논의할게요.

자, 시작해 볼게요!

출처: Rust 공식문서

본문

이번 장의 서문이자 전체 스마트 포인터 개념에 대한 준비 단계예요. 여기서는 개념적인 그림을 먼저 잡고, 이후 각 절에서 구체적인 스마트 포인터 타입을 하나씩 다뤄요.

  • Box<T> — 값이 힙에 저장되도록 해 주는 가장 단순한 스마트 포인터예요. 재귀 타입이나 컴파일 타임에 크기를 모르는 타입을 다룰 때 유용해요.
  • Rc<T> — 같은 데이터를 여러 곳에서 공유하면서 각 소유자 수를 세는 레퍼런스 카운팅 타입이에요. 참조 횟수가 0이 되면 데이터가 정리돼요.
  • RefCell<T>Ref<T>, RefMut<T> — 불변 데이터라도 내부 값을 변경할 수 있게 해 주는 내부 가변성 패턴의 핵심이에요. 빌림 규칙을 컴파일 타임이 아니라 런타임에 검사해요.

이 절에서 핵심적으로 짚어야 할 건, 스마트 포인터가 레퍼런스와 근본적으로 다른 점이 데이터를 소유한다는 사실이라는 거예요. 그래서 Deref로 값에 접근하는 방법을, Drop으로 소멸 시점의 동작을 각각 제어하게 되죠.

더 알아보기

  • 스마트 포인터라는 이름의 출발점인 Box<T>부터 차근차근 보면 좋아요. 각 절에서 DerefDrop 트레이트가 어떻게 쓰이는지 자연스럽게 이어져요.
  • 내부 가변성과 레퍼런스 사이클은 개념이 조금 낯설 수 있는데, Rc<T>RefCell<T>를 함께 쓰는 패턴을 직접 만들어 보면 이해가 빨라져요.
  • 이번 장에서 Box<T>를 힙에 할당한다고 했지만, 구체적으로 어떻게 동작하는지는 이후 절들에서 예제와 함께 설명해요.