https://rust-book.cs.brown.edu/ch04-00-understanding-ownership.html
Understanding Ownership - The Rust Programming Language
Ownership is Rust’s most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it’s important to understand how ownership works. In this chapter,
rust-book.cs.brown.edu
서론
러스트 언어에는 독특한 소유권 개념이라는 것이 있다.
소유권 개념을 만든 본질적인 목표는 컴파일 과정에서 부적절한 메모리 사용 상황을 "미리" 감지하고자 함에 있다고 생각한다.
만약 컴파일러가 안전하지 않은 메모리 사용 시나리오를 확인하고, 이를 이유로 완성된 바이너리를 토해내지 않겠다고 선언하면 어떠한 손해와 이익이 있을까?
내 생각에는 기존 low level 언어(주로 메모리 관리를 집적해줘야 하는 C, C++ 같은 것들)들로 만들어진 프로그램에서 겪을 수 있었던 단골손님과 같은 런타임 memory violation과 같은 상황을 겪을 가능성을 현저히 줄이는 이익이 있다고 생각한다.
기존 low level 기반 프로그램은 안전하지 않은 상태로 완성되어 배포되어 왔고, 배포 이후 에러 리포트(심지어 이러한 에러 리포트를 받기 위해서도 상당한 비용이 증가함)를 확인 후 다시 유지보수하는 고통을 감내하는 것이 업계 관례였는데,
배포되기 이전에 메모리 불안정성을 점검하고 완성도 있는 프로그램을 계속해서 배포할 수 있다면 그리고 이 것을 보증해 줄 수단만 있다면 확실히 고통이 줄어들 것 같다. 바로 이 보증 수단이 러스트언어에서의 "소유권" 개념이라고 생각한다.
물론 이 소유권 개념에 대한 개념이 언어에 대한 진입장벽을 높이겠지만 확실히 익숙해지고 나서는 유지 보수 측면에서 유리할 것 같다.
그렇다면 손해는 무엇일까? 문서를 얕게 이해해 본 지금의 단계에서는 높은 언어 학습 진입장벽, 그리고 비동기 흐름의 프로그램 개발에서의 불편함들이 예상된다.
여하튼 이 언어의 철학은 컴파일 타임에 최대한 많은 것을 확인하고 런타임에는 최소한의 확인을 하자는 목표가 있다고 하니 이것을 늘 염두에 두고 언어 개념을 이해하려고 한다면 좀 더 도움이 될 것 같다는 게 내 생각이다.
(A foundational goal of Rust is to ensure that your programs never have undefined behavior. A secondary goal of Rust is to prevent undefined behavior at compile-time instead of run-time)
본론
고전적 언어에서 자주 겪는 Segmentation fault 과같은 런타임 에러는 주로 Heap 영역(segment)에 할당된 즉, 런타임에 동적으로 할당된 메모리 영역에서의 부적절한 행위에 의해 일어난다.
소유권이라는 개념도 결국 이러한 문제를 컴파일 단계에서 미리 확인하고자 하는 것이다.
따라서 소유권 개념도 heap 메모리 영역에서의 위험성을 논하는 개념이기에 러스트에서 heap 영역 메모리에 데이터 할당을 담당하는 `Box`라는 타입에 대한 기본적 이해가 필요하다.
BOX Type
Box는 Rust의 가장 기본적인 힙 할당 스마트 포인터이다. Box를 사용하면 데이터를 스택 대신 힙에 저장할 수 있다.
그리고 아래의 예시와 같이, Box로 메모리를 heap에 할당하고 그것을 가리키는 포인터를 a변수로 설정 후, `let b = a` 와 같은 연산을 하면 deep copy가 일어나는 대신 swallow copy가 일어난다.
명시적 메모리 관리 금지 조약
러스트는 힙에 할당된 메모리를 명시적으로 해제하는 동작을 지양한다. 즉, C++나 C과 같은 low level언어에서 동적할당된(malloc) 메모리를 더 이상 사용하지 않을 때 free를 명시적으로 호출하는 것과 같은 행동을 금기시하겠다는 것이다.
이러한 철학을 고수하는 이유에 대해 고찰해 보면, low level 언어에서 우리가 수많이 저질러 왔던 실수들을 생각해 볼 수 있는데, 대표적으로 하나의 힙 메모리 A가 있고, A를 바라보는 여러 pointer들이 있을 때, 의도치 않은 타이밍에 어떤 context에서 A를 free()하여 운영체제에 메모리를 반납한 후 허공을 바라보는 여러 pointer들이 저지를 재앙적인 행위들에 대해 생각해 보자.
이와 같이 프로그래머(사람)는 늘 실수를 저질러 왔고, 이러한 명시적 메모리 관리 방식은 수많은 재앙을 불러왔기에 이러한 방식은 더 이상 안전하지 않다고 판단한 언어 개발자의 철학이지 않을까 생각한다.
그래서 가비지 컬렉터도 사용하기 싫고, 명시적 메모리 해제도 싫다면 어떻게 힙 메모리를 불필요한 타이밍에 해제할 것인가 말인가??? 내 생각엔 러스트는 소유권을 이용해 안전한 메모리 해제 기준을 잡으려 하는 것 같다.
소유권
공식 문서를 읽고 이해한 소유권 개념의 핵심은 다음과 같다.
A. 힙에 할당된 메모리(Box로 생성된 힙 메모리)는 단 하나의 소유권만 판매한다.
B. 이 소유권을 가지는 주체는 힙 메모리를 바라보는 포인터 변수들이다.
C. 소유권은 단 하나의 포인터 변수만 가질 수 있다.
D. 소유권을 가진 포인터 변수가 해제될 때, 포인터가 바라보던 힙 메모리도 자동으로 해제(drop, free)된다.
여기까지 보면 단 한 번의 힙 메모리 해제만 발생하게 되므로, 여러 번 힙 메모리 해제를 시도할 때 발생하는 고전적 문제들이 해결될 수 있어 보인다. 다음과 같이 말이다.
그렇지만 아직 또 하나의 고전적 문제인 `이미 해제된 힙 메모리에 대한 접근(Read, Write) 문제는 어떻게 해결될 수 있는 것인가?` 에 대한 의문은 여전히 남아있다.
이 의문에 대한 해답을 요약하자면 `러스트 컴파일러가 모두 점검해준다.`이다. 어떻게 점검해 준다는 것일까?
이 의문에 대한 답을 찾기 앞서 먼저 소유권이 어떻게 거래되는지부터 이해할 필요가 있다.
아래는 힙 메모리에 대한 소유권이 이전되는 여러 사례들인데, 간단히 생각해 보면 소유권을 가지고 있던 포인터가 다른 포인터 변수에 대입 연산(=)이 발생하면 소유권이 넘어간다.
대입 연산은 말 그대로 `let b = a`도 될 수 있고, 함수의 반환과 동시에 리턴되는 값을 전달받는 것 일수도 있고, 함수 파라미터로 포이터 변수를 넘겨주는 것일 수도 있다.
위와 같은 여러 가지 소유권이 이전되는 시나리오가 있는데 본질적으로는 어떠한 힙 메모리를 바라보는 포인터변수를 다른 포인터 변수에 대입되는 순간 소유권이 이전되는 것이다.
그렇다면, 소유권이 넘어가게 되면 어떤 일이 발생하는가?
소유권이 넘어간 다음 소유권을 넘겨준 변수에 대한 read/write 시도 시 아래와 같은 컴파일 에러가 발생하게 된다.
즉, 소유권을 넘겨준 포인터 변수는 더 이상 사용할 수 없는 상태가 된다는 뜻이다.
여기까지 확인했으면 `이미 해제된 힙 메모리에 대한 접근(Read, Write)은 문제는 어떻게 해결될 수 있는 것인가?` 에 대한 의문은 해결됐을 것이다. 단 하나의 포인터 변수만 힙 메모리에 대한 사용권을 가질 수 있다는 사실을 확인했기 때문에 이 문제에 대한 해답을 얻었을 것으로 기대한다.
이다음 글로 소유권 개념에 이어서 `참조 및 대여(References and Borrowing)` 개념에 대해 생각 정리를 좀 해보겠다.
'IT' 카테고리의 다른 글
Rust lang - 참조 및 대여에 대한 생각 정리 (0) | 2025.04.18 |
---|---|
Effective Typescript - 11 - 잉여 속성 체크 한계 인지하기 (0) | 2022.01.25 |
Effective Typescript - 10 - 객체 래퍼 타입 피하기 (0) | 2022.01.25 |
Effective Typescript - 9 - 타입 단언보다는 타입 선언을 사용하기 (0) | 2022.01.25 |
Effective Typescript - 8 - 타입 공간과 값 공간의 심벌 구분하기 (0) | 2022.01.24 |