| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 콜렉션
- package
- map
- DART
- 웹크롤러
- python
- 플러터
- animation
- 클래스
- textstyle
- import
- crawler
- set
- pushnamed
- Class
- text
- ML
- Android
- 다트
- function
- Collection
- 크롤러
- Flutter
- rust
- variable
- 파이썬
- 코틀린
- 함수
- kotlin
- List
- Today
- Total
조용한 담장
Rust Understanding Ownership - Quick Reference 본문
이 글은 Rust 공식 문서 4장(Understanding Ownership)의 내용을 기반으로 빠르게 훑어볼 수 있도록 정리한 요약본입니다.
4. Understanding Ownership
Rust가 가비지 컬렉터 없이 메모리 안전성을 보장하는 핵심 메커니즘입니다.
"누가 메모리를 해제할 책임이 있는가?"를 결정하는 규칙들의 집합입니다.
4.1. What Is Ownership?
https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
The 3 Rules
- Rust의 각각의 값은 해당 값의 Owner(소유자)라고 불리는 변수가 있다.
- 한 번에 딱 하나의 Owner만 존재할 수 있다.
- Owner가 스코프 밖으로 벗어나면, 값은 버려진다(Dropped).
메모리 동작: Move vs Copy
데이터 타입이 힙(Heap)을 쓰느냐 스택(Stack)을 쓰느냐에 따라 대입 연산의 동작이 다릅니다.
Case 1: Move (힙 데이터 - String 등)
힙에 저장되는 데이터는 변수 대입 시 얕은 복사(포인터 복사)가 일어나며, 기존 변수는 무효화됩니다. 이를 Move라고 합니다.
#[test]
fn test_ownership_move() {
let s1 = String::from("hello");
let s2 = s1; // 소유권이 s2로 이동(Move)됨.
// println!("{}, world!", s1); // 컴파일 에러! s1은 이제 유효하지 않음 (Use of moved value)
println!("{}, world!", s2); // 정상 작동
}
Case 2: Copy (스택 데이터 - 정수, 불리언 등)
크기가 고정된 스택 데이터는 Copy 트레이트가 구현되어 있어, 대입 시 값이 자동 복사됩니다. 기존 변수도 계속 사용 가능합니다.
#[test]
fn test_stack_copy() {
let x = 5;
let y = x; // x의 값이 복사되어 y에 할당됨.
println!("x = {}, y = {}", x, y); // 둘 다 사용 가능
}
Case 3: Clone (깊은 복사)
힙 데이터를 그대로 유지하면서 새로 하나 더 만들고 싶다면 .clone()을 명시적으로 호출해야 합니다.
#[test]
fn test_deep_copy() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 힙 메모리 데이터까지 완전히 복사 (비용 발생)
println!("s1 = {}, s2 = {}", s1, s2); // 둘 다 사용 가능
}
Quick Tip:
- 함수에 값을 전달할 때도
Move가 발생합니다. 값을 함수에 넘겨주고 다시 쓰고 싶다면 반환값으로 돌려받거나, 아래의 '참조(Reference)'를 써야 합니다.
4.1. What Is Ownership? - 테스트 코드(Rust Playground)
Gist
4.2 References and Borrowing
https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
소유권을 넘기지(Move) 않고 값에 접근하고 싶을 때 사용합니다. & 기호를 사용합니다.
참조의 규칙 (The Rules of References)
- 어느 한 시점에 코드는 하나의 가변 참조자(
&mut) 또는 여러 개의 불변 참조자(&) 중 하나만 가질 수 있다. (동시 존재 불가) - 참조자는 항상 유효해야 한다. (Dangling Reference 불가)
불변 참조 (&T) vs 가변 참조 (&mut T)
1. 여러 개의 불변 참조 (Read-Only)
읽기만 한다면 몇 명이든 상관없습니다.
#[test]
fn test_immutable_borrow() {
let s = String::from("hello");
let r1 = &s; // 불변 참조 1
let r2 = &s; // 불변 참조 2
println!("{}, {}", r1, r2); // 문제 없음
}
2. 단 하나의 가변 참조 (Read-Write)
수정 권한은 오직 한 명에게만 주어집니다. 데이터 레이스(Data Race)를 원천 차단합니다.
#[test]
fn test_mutable_borrow() {
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // 컴파일 에러! (Second mutable borrow occurs here)
r1.push_str(", world");
println!("{}", r1);
}
3. 불변과 가변의 혼용 불가
"보고 있는데 누가 수정하면 안 된다"는 원칙입니다.
#[test]
fn test_mixed_borrow() {
let mut s = String::from("hello");
let r1 = &s; // 불변 참조
let r2 = &s; // 불변 참조
// let r3 = &mut s; // 컴파일 에러! (불변 참조가 살아있는 동안 가변 참조 불가)
println!("{}, {}", r1, r2);
// NLL (Non-Lexical Lifetimes):
// r1, r2가 여기서 마지막으로 사용되었으므로, 이 시점 이후부터는 가변 참조 가능
let r3 = &mut s;
r3.push_str(" world");
println!("{}", r3); // 최신 Rust 컴파일러는 여기서 문제 없음
}
** Quick Tip (Dangling Reference):**
함수 내에서 생성한 로컬 변수의 참조(reference)를 리턴하면 안 됩니다. 함수가 끝나면 로컬 변수는 Drop되는데, 포인터만 리턴하려 하기 때문입니다. 이럴 땐 값을 직접 리턴(Move)하세요.
4.2 References and Borrowing - 테스트 코드(Rust Playground)
Gist
4.3 The Slice Type
https://doc.rust-lang.org/book/ch04-03-slices.html
소유권을 갖지 않고(Borrow), 컬렉션(배열, 문자열 등)의 연속된 일부분을 참조하는 뷰(View)입니다.
문자열 슬라이스 (&str)
String의 일부분을 가리킵니다. 내부적으로 ptr(시작 위치)와 len(길이)만 저장합니다.
#[test]
fn test_string_slices() {
let s = String::from("Hello World");
let hello = &s[0..5]; // 인덱스 0부터 4까지
let world = &s[6..11]; // 인덱스 6부터 10까지
assert_eq!(hello, "Hello");
assert_eq!(world, "World");
// 전체 슬라이스 문법
let slice = &s[..];
}
문자열 리터럴은 슬라이스다
코드에 박혀있는 문자열은 바이너리 어딘가를 가리키는 슬라이스입니다.
let s: &str = "Hello"; // s는 바이너리 데이터 영역의 특정 위치를 가리키는 슬라이스
배열 슬라이스 (&[T])
문자열뿐만 아니라 일반 배열도 슬라이싱 가능합니다.
#[test]
fn test_array_slices() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // [2, 3]
assert_eq!(slice, &[2, 3]);
}
실전 팁: 함수 파라미터 설계
함수 파라미터로 문자열을 받을 때 &String보다 &str을 사용하는 것이 훨씬 유연합니다.
// Bad: String 객체의 참조만 받을 수 있음
fn bad_greet(name: &String) { /* ... */ }
// Good: String의 참조도 받고, 문자열 리터럴("...")도 받을 수 있음
fn good_greet(name: &str) {
println!("Hello, {}!", name);
}
#[test]
fn test_api_design() {
let my_name = String::from("Rust");
// bad_greet("Rust"); // 컴파일 에러 (타입 불일치)
bad_greet(&my_name); // 가능
good_greet("Rust"); // 리터럴 가능 (&str)
good_greet(&my_name); // String 참조 가능 (Deref Coercion에 의해 &String -> &str 자동 변환)
good_greet(&my_name[0..1]); // 슬라이스 가능
}
'Rust' 카테고리의 다른 글
| Rust Using Structs to Structure Related Data - Quick Reference (0) | 2026.01.02 |
|---|---|
| Rust Common Programming Concepts - Quick Reference (0) | 2025.12.22 |