스마트 포인터

포인터 (pointer) 는 메모리의 주솟값을 담고 있는 변수에 대한 일반적인 개념입니다. 이 주솟값은 어떤 다른 데이터를 참조합니다. 혹은 바꿔 말하면, ‘가리킵니다.’ 러스트에서 가장 흔한 종류의 포인터는 4장에서 배웠던 참조자입니다. 참조자는 & 심볼로 표시하고 이들이 가리키고 있는 값을 빌려옵니다. 이들은 값을 참조하는 것 외에 다른 어떤 특별한 능력은 없으며, 오버헤드도 없습니다.

한편, 스마트 포인터 (smart pointer) 는 포인터처럼 작동할 뿐만 아니라 추가적인 메타데이터와 능력들도 가지고 있는 데이터 구조입니다. 스마트 포인터의 개념은 러스트 고유의 것이 아닙니다: 스마트 포인터는 C++로부터 유래되었고 다른 언어들에도 존재합니다. 러스트의 표준 라이브러리에는 다양한 종류의 스마트 포인터들이 정의되어 있는데 이를 통해 참조자가 제공하는 것 이상의 기능을 제공합니다. 일반적인 개념을 탐구하기 위하여 몇 가지 스마트 포인터 예제를 살펴보려고 하는데, 그중에는 참조 카운팅 (reference counting) 스마트 포인터 타입이 있습니다. 이 포인터는 소유자의 개수를 계속 추적하고, 더 이상 소유자가 없으면 데이터를 정리하는 방식으로, 어떤 데이터에 대한 여러 소유자를 만들 수 있게 해 줍니다.

소유권과 대여의 개념을 가지고 있는 러스트에서, 참조자와 스마트 포인터 사이에는 추가적인 차이점이 있습니다: 참조자가 데이터를 빌리기만 하는 반면, 대부분의 경우 스마트 포인터는 가리킨 데이터를 소유합니다.

우리는 이미 이 책에서 8장의 StringVec<T>와 같은 몇 가지 스마트 포인터들을 마주쳤습니다. 비록 그때는 이것들을 스마트 포인터라고 부르지 않았지만요. 이 두 타입 모두 스마트 포인터로 치는데 그 이유는 이들이 어느 정도의 메모리를 소유하고 이를 다룰 수 있게 해 주기 때문입니다. 그들은 또한 메타데이터와 추가 능력 또는 보장성을 갖고 있습니다. 예를 들어 String은 자신의 용량을 메타데이터로 저장하고 자신의 데이터가 언제나 유효한 UTF-8 임을 보증하는 추가 능력도 가지고 있습니다.

스마트 포인터는 보통 구조체를 이용하여 구현되어 있습니다. 보통의 구조체와는 달리 스마트 포인터는 DerefDrop 트레이트를 구현합니다. Deref 트레이트는 스마트 포인터 구조체의 인스턴스가 참조자처럼 동작하도록 하여 참조자 혹은 스마트 포인터와 함께 작동하는 코드를 작성할 수 있도록 해줍니다. Drop 트레이트는 스마트 포인터의 인스턴스가 스코프 밖으로 벗어났을 때 실행되는 코드를 커스터마이징 가능하도록 해 줍니다. 이번 장에서는 이 두 개의 트레이트 모두를 다루고 이들이 왜 스마트 포인터에게 중요한지 보여줄 것입니다.

스마트 포인터 패턴이 러스트에서 자주 사용되는 일반적인 디자인 패턴임을 생각하면, 존재하는 모든 스마트 포인터를 이번 장에서 다루지는 못할 것입니다. 많은 라이브러리가 자신만의 스마트 포인터를 가지고 있고, 심지어 여러분도 자신만의 것을 작성할 수 있습니다. 여기서는 표준 라이브러리에 있는 가장 일반적인 스마트 포인터들을 다루겠습니다:

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

추가로 불변 타입이 내부 값을 변경하기 위하여 API를 노출하는 내부 가변성 (interior mutability) 패턴에 대해서 다루겠습니다. 또한 순환 참조 (reference cycles) 가 어떤 식으로 메모리가 새어나가게 할 수 있으며, 이를 어떻게 방지하는지에 대해서도 논의해 보겠습니다.

함께 뛰어들어 볼까요!