콘텐츠로 바로가기

Rust Safety & Ownership

Rust Safety 및 Ownership의 정의, 범위, 선행 지식, 학습 주제, 참고 근거를 정리한 CS&E 학습 노드입니다.

Article
M

Me

hyunyoun's Blog

programming-languages-compilersprogramming-languagescompilerslanguage-platformsecosystemsrust-safetyownershiplanguages-compilers9 min read

1. Overview

Rust의 안전성과 소유권(Rust Safety & Ownership)가비지 컬렉터(GC) 없이 C/C++ 수준의 제로 코스트(Zero-cost) 성능을 지향하면서도, 컴파일 타임에 메모리 누수, 댕글링 포인터, 데이터 경쟁(Data Race) 위험을 줄이는 시스템 프로그래밍 언어의 설계를 다룹니다.

학습자는 Rust의 메모리 관리 모델인 **소유권(Ownership)**과 데이터 이동(Move)의 의미를 살펴봅니다. 나아가 **빌림(Borrowing)**과 불변/가변 참조자(&T, &mut T)의 배타성 규칙을 빌림 검사기(Borrow Checker)가 어떻게 검증하는지 정리합니다. 마지막으로 다중 스레드 간 상태 공유 시 컴파일러가 보장하는 **Send / Sync 트레이트(Traits)**와 동시성 안전성, 그리고 컴파일 타임 다형성인 제네릭 트레이트 메커니즘까지 익혀 현대 시스템 프로그래밍의 핵심 역량을 확보합니다.

2. Scope & Boundaries

In-Scope

  • 소유권 시스템 (Ownership System): 3가지 소유권 규칙, Move(이동) 의미론, Drop(소멸) 타이밍, 얕은/깊은 복사(Clone).
  • 빌림과 참조 (Borrowing & References): 불변 참조(&T), 가변 참조(&mut T), 다중 불변 vs 단일 가변(Aliasing vs Mutability) 배타성 규칙.
  • 수명 (Lifetimes): 댕글링 참조 방지, 라이프타임 어노테이션('a), 라이프타임 생략 규칙(Elision Rules).
  • Fearless Concurrency (동시성): 데이터 경쟁 차단, Send / Sync 마커 트레이트, Arc<Mutex<T>> 기반 안전한 상태 공유.

Out-of-Scope

  • Rust 비동기 생태계 (Async Rust): tokio, async/await 심화 내부(Pin, Future) → 전문 Rust 백엔드 프레임워크 영역.
  • 매크로 시스템 (Macros): macro_rules!, 절차적 매크로(Proc-macro) 작성 → 메타프로그래밍 심화 영역.

Boundaries

  • C++ 스마트 포인터 vs Rust 소유권: C++의 std::unique_ptr은 런타임 오버헤드가 없지만 std::move 후 접근(Use-after-move)하면 런타임에 크래시될 수 있습니다. Rust의 소유권은 move 발생 후 해당 변수 접근 자체를 **컴파일러가 에러 처리(Borrow Checker)**하여 런타임 문제의 가능성을 컴파일 타임에 줄입니다. 이 엄격함이 Rust의 높은 학습 곡선의 원인이자 안전성의 기반입니다.

3. Counterexample

  • Use-After-Move와 컴파일러 방어 (Rust Move Semantics): let s1 = String::from("hello"); let s2 = s1; println!("{}", s1);. 힙에 할당된 문자열 s1의 소유권이 s2로 전이(Move)되었습니다. C++이나 Python에서는 s1이 얕은 복사되어 여전히 접근 가능하거나 런타임에 문제를 일으킬 수 있지만, Rust 컴파일러는 세 번째 줄에서 error[E0382]: borrow of moved value: s1 컴파일 에러를 내고 빌드를 거부합니다. 메모리 이중 해제(Double Free)를 언어 설계 레벨에서 막는 방식입니다.
  • 멀티스레드 데이터 경쟁 차단 (Data Race Prevention): let mut counter = 0; thread::spawn(move || { counter += 1; }); counter += 1;. 두 스레드가 가변 상태 counter를 동시에 수정하려는 시도. C++나 Java에서는 런타임에 값이 깨지는 경쟁 조건(Race Condition)이 발생하지만, Rust에서는 다수의 소유자나 가변 참조자를 허용하지 않는 규칙 덕분에 컴파일 타임에 error[E0382]: use of moved value 에러로 차단됩니다. 이를 해결하려면 명시적으로 Arc<Mutex<T>>로 감싸 안전함을 증명해야 합니다.

4. Prerequisites

  • 포인터와 힙 메모리 (Basic): 값 타입과 참조 타입, 스택과 힙 메모리 차이. (03-01 Memory & CPU Architecture)
  • C/C++ 메모리 버그 (Recommended): C/C++의 메모리 누수와 데이터 경쟁 버그 경험이 있어야 Rust의 필요성을 통감할 수 있습니다. (05-04-01 C & Modern C++)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 Ownership & Move Rules "값의 소유자는 단 하나이며, 스코프 종료 시 즉시 해제된다"는 GC 없는 메모리 해제 규칙을 익힙니다. P1
2 Borrowing & Lifetimes 여러 번의 읽기(&)는 허용하나, 쓰기(&mut)는 단독으로만 허용하는 빌림 배타성 규칙과 댕글링 방지를 살펴봅니다. P5
3 Fearless Concurrency (Send/Sync) 컴파일 타임에 데이터 경쟁을 차단하고 Arc<Mutex<T>>로 안전하게 공유하는 병렬 프로그래밍을 살펴봅니다. Industry
4 Traits & Zero-cost Abstraction 다형성을 인터페이스(Trait)로 정의하고 단형성화(Monomorphization)로 런타임 비용 0을 지향하는 구조를 이해합니다. Industry

6. Learning Topics

Basic

Core Topic 01: 값의 주인은 오직 하나, 소유권과 이동 (Ownership & Move Rules)

  • Why to Learn: "왜 다른 언어에선 잘 되던 코드가 Rust에선 컴파일 에러가 날까?"의 근본 원인인 소유권 철학을 체득해야, 컴파일러(Borrow Checker)의 규칙을 이해하고 메모리 안전성을 확보할 수 있기 위함입니다.
  • What to Learn:
    • Concepts: 소유권(Ownership) 3규칙 (1값 1소유자, 소유자 스코프 종료 시 값 폐기), 이동(Move Semantics), 복사 트레이트(Copy), 깊은 복사(Clone).
    • Skills: 스코프에 따른 Drop 자동 호출 확인, Move 후 변수 사용 에러 해결.
  • How to Learn:
    • 1단계: 소유권 규칙: let s = String::from("hello"); { let s2 = s; } println!("{}", s);. 블록 안에서 s의 소유권이 s2로 전이되고 블록 종료 시 s2가 Drop(메모리 해제). 따라서 블록 밖에서 s 사용 시 컴파일 에러. GC 없이 스코프 기반 메모리 관리를 달성하는 규칙을 살펴봅니다.
    • 2단계: Copy 타입: 정수, 부울 등 스택에만 존재하는 크기 고정 타입은 Move 대신 비트 복사(Copy)가 일어납니다. let x = 5; let y = x; println!("{}", x);는 에러가 나지 않는 힙/스택 타입 간의 차이를 살펴봅니다.
  • Implement: 파이썬 의사 코드(Pseudo Code)로 Rust 소유권 시뮬레이터 구현. Variable 클래스 생성. move(from_var, to_var) 호출 시 from_var의 내부 포인터를 None으로 비우고. 접근 시 UseAfterMoveException 발생 로직 구현. "컴파일 타임 댕글링 방지" 메커니즘 가시화.

Core Topic 02: 읽거나 쓰거나, 빌림과 라이프타임 (Borrowing & Lifetimes)

  • Why to Learn: 소유권을 매번 넘겨주는 것은 비효율적이므로, 포인터(참조)를 안전하게 빌려 쓰는 빌림 규칙(Aliasing vs Mutability)과, 빌려준 주소가 원본 해제 후 참조(Dangling)되지 않음을 보장하는 라이프타임 검증을 익히기 위함입니다.
  • What to Learn:
    • Concepts: 불변 참조(&T), 가변 참조(&mut T), 빌림 배타성(다수 &T OR 단일 &mut T), 댕글링 참조 차단, 라이프타임 어노테이션('a).
    • Skills: 빌림 검사(Borrow Checker) 에러 해석(cannot borrow as mutable), 생략된 라이프타임 유추.
  • How to Learn:
    • 1단계: 빌림 배타성: 독서실 책(데이터). 여러 명이 동시에 읽기(&T) 가능. 누군가 연필로 고치기(&mut T) 시작하면 다른 누구도 읽거나 고칠 수 없음(단독 접근 보장). 이 규칙이 Data Race와 Iterator 무효화 버그를 차단하는 방식을 살펴봅니다.
    • 2단계: 라이프타임: 함수 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str. 반환된 참조자의 수명은 인자로 들어온 x, y 중 짧은 수명('a)에 종속됨을 명시. x가 해제된 후 반환값을 쓰려 하면 컴파일러가 라이프타임 불일치로 차단하는 댕글링 방지 방식을 살펴봅니다.
  • Implement: Rust 코드 스니펫 디버깅 실습 (파이썬 시뮬레이션). 불변 참조 ref1, ref2 생성 후 가변 참조 ref3 생성 시도 시 BorrowError("Cannot borrow as mutable because it is also borrowed as immutable") 발생기 작성. 빌림 상태 머신 추적 로직 데모.

Practical

Core Topic 03: 두려움 없는 동시성, 스레드 안전성과 Send/Sync (Fearless Concurrency)

  • Why to Learn: C/C++ 멀티스레딩에서 자주 발생하는 데이터 경쟁(Data Race)을 Rust가 컴파일 타임에 SendSync 타입 마커로 제한하는 방식을 이해하고, 병렬 프로그래밍 안전성을 높이기 위함입니다.
  • What to Learn:
    • Concepts: 스레드 간 상태 공유(Shared State), std::thread::spawn, 마커 트레이트(Send, Sync), Mutex<T> (상호 배제 락 내장), Arc<T> (원자적 참조 계수 공유).
    • Skills: Arc<Mutex<T>> 패턴으로 스레드 간 가변 상태 안전한 공유 구현.
  • How to Learn:
    • 1단계: SendSync: Rc<T>(단일 스레드 참조 계수)는 Send 마커가 없어 스레드 경계를 넘으려 하면 컴파일 에러. 스레드 안전한 Arc<T>(Atomic RC)를 써야만 컴파일 통과. 런타임 스레드 안전성을 타입 시스템으로 보장하는 방식을 살펴봅니다.
    • 2단계: Mutex<T>의 소유권 결합: C++에서는 락(Lock)과 보호할 데이터가 분리되어 락 없이 데이터에 접근할 위험이 있습니다. Rust의 Mutex<T>는 데이터를 감싸는 박스. 데이터에 접근하려면 반드시 lock().unwrap()을 호출해 가변 참조(&mut T)를 얻어야만 하는 강제 설계를 살펴봅니다.
  • Implement: Rust 동시성 멀티스레딩 패턴 파이썬 모사. 10개 스레드에서 공용 카운터 증가시키기. 단순 전역 변수 공유 시 (GIL 해제 가정) Data Race 결과 관찰. ArcMutex(initial_val) 객체를 만들어 with arc_mutex.lock() as val: val += 1 패턴 강제하는 API 작성 후 안전성 보장 데모.

Advanced

Core Topic 04: 비용 없는 다형성, 트레이트와 단형성화 (Traits & Zero-cost Abstraction)

  • Why to Learn: Java의 인터페이스(Interface)와 달리 런타임 가상 메서드 테이블(vtable) 비용 없이, 컴파일 타임 템플릿 확장을 통해 기계어 레벨 성능을 확보하는 제네릭(Generic)과 트레이트(Trait)의 최적화 구조를 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: 트레이트(Trait, 행위 정의), 트레이트 바운드(T: Display), 정적 디스패치(Static Dispatch)와 단형성화(Monomorphization), 동적 디스패치(Dynamic Dispatch, dyn Trait).
    • Skills: 커스텀 Trait 구현, 제네릭 함수 작성, 정적 vs 동적 디스패치 선택 트레이트오프.
  • How to Learn:
    • 1단계: Trait: 공통 동작 정의. trait Summary { fn summarize(&self) -> String; }. 구조체 ArticleTweet에 각각 구현. fn notify<T: Summary>(item: &T)는 Summary가 구현된 아무 타입이나 받는 제네릭 다형성을 살펴봅니다.
    • 2단계: 단형성화(Monomorphization): 컴파일러는 notify(article)notify(tweet) 호출을 보고, 런타임 확인 대신 notify_Article(..)notify_Tweet(..) 이라는 두 개의 구체적 기계어 함수를 자동 복제 생성(Static Dispatch). 런타임 다형성 오버헤드가 제로가 되는 원리를 살펴봅니다.
  • Implement: C++ Template 메커니즘 비교 파이썬 코드. fn_generic(T obj) 호출. 실행 전(컴파일 타임 시뮬레이션) 사용된 타입 목록([Article, Tweet])을 수집하여 각 타입 전용 함수 문자열을 생성(Code Generation)하는 스크립트 작성. 단형성화로 인한 바이너리 크기 증가 vs 런타임 속도 트레이드오프 수치 로그.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
Ownership 힙(Heap) 메모리의 특정 데이터에 대해 오직 단 하나의 변수(소유자)만이 접근 및 해제 권한을 독점하게 강제하는 Rust의 코어 메모리 관리 규칙입니다. 기본 메모리 해제 보장 Move Semantics Garbage Collection 가비지 컬렉터(GC) 없이 컴파일 타임에 free()를 자동 삽입하기 위한 논리적 규칙임 P1:CS2023 core
Borrowing 소유권을 넘기는(Move) 대신, 데이터의 참조(포인터)만 임시로 빌려주어(Borrow) 원본을 안전하게 읽거나 쓸 수 있게 하는 메커니즘입니다. 권장 데이터 임시 접근 References (&, &mut) Pointer C/C++의 포인터와 구조적으로 비슷하지만, 컴파일러가 수명을 엄격하게 감시한다는 점이 다름 P5:SFIA core
Borrow Checker 코드가 컴파일될 때, 여러 곳에서 동시에 데이터를 빌려가 쓰면서 데이터 레이스(Data Race)가 일어나지 않는지 검사하는 컴파일러 내부의 정적 분석기입니다. 실무 동시성 버그 차단 Lifetime Linter 런타임에 성능을 깎아먹는 엔진이 아니라, 100% 컴파일 타임(빌드 시)에 동작하는 정적 분석기임 Industry core
Lifetime 빌린 참조(Borrow)가 원본 데이터보다 더 오래 살아남아 허공(Dangling Pointer)을 가리키지 않도록, 참조의 유효 기간을 컴파일러에게 증명하는 타입 시스템의 연장선입니다. 심화 댕글링 포인터 방지 Lifetime Annotations ('a) Garbage Collection 라이프타임 명시 기호('a)를 쓴다고 해서 데이터 수명이 연장되는 것이 아니라, 컴파일러를 납득시키는 용도임 Industry core

8. References

Primary

  • [P1] CS2023 - Programming Languages (PL) - Type Systems (Ownership and Linear Types)
  • [P5] SFIA - Software Development (PROG) - Systems Programming

Secondary

  • [The Rust Programming Language] Steve Klabnik, Carol Nichols - Ownership, Borrowing, and Lifetimes
  • [Programming Rust] Jim Blandy - Memory Safety and Concurrency

Industry

  • [Rust Foundation] - Rustonomicon (The Dark Arts of Unsafe Rust)
  • [Microsoft Security Response Center (MSRC)] - Why Rust for Safe Systems Programming

9. Final Checklist

Primary

  • 변수를 다른 변수에 대입할 때 발생하는 '이동(Move)' 시맨틱이 어떻게 얕은 복사(Shallow Copy)로 인한 이중 해제(Double Free) 버그를 차단하는지 증명할 수 있는가?
  • 참조자(Borrow) 규칙 중 "여러 개의 불변 참조(&T)는 가능하지만, 가변 참조(&mut T)는 오직 하나만 존재해야 한다"는 규칙이 의미하는 바를 설명할 수 있는가?

Secondary

  • 멀티스레드 환경에서 가변 참조(&mut T)가 하나만 존재한다는 Rust의 컴파일러 규칙이, 어떻게 뮤텍스 락(Mutex Lock) 없이도 데이터 레이스(Data Race)를 차단하는지 설명할 수 있는가?
  • 댕글링 포인터(Dangling Pointer)가 발생하는 C++의 시나리오를 제시하고, Rust의 라이프타임(Lifetime) 추론이 이를 컴파일 타임에 어떻게 거부(Reject)하는지 묘사할 수 있는가?

Industry

  • 가비지 컬렉션(GC)으로 인한 런타임 STW(Stop-The-World) 지연 시간을 허용하기 어려운 임베디드, HFT(고빈도 거래), OS 커널 분야에서 Rust가 대안이 되는 아키텍처적 이유를 논증할 수 있는가?
  • Rust의 무결성을 잠시 포기하고 C 라이브러리(FFI)와 통신하기 위해 unsafe 블록을 열었을 때, 이곳에서 터질 수 있는 보안 취약점을 어떻게 격리하고 검증할지 설계할 수 있는가?

Languages Compilers · Language Platforms & Ecosystems

2 / 6