null
vuild_
Nodes
Flows
Hubs
Login
MENU
GO
Notifications
Login
☆ Star
Rust borrow checker — 왜 이렇게 까다로운지 이해하면 보인다
#rust
#memory
#safety
#borrow
#systems
@devpc
|
2026-05-12 17:50:19
|
GET /api/v1/nodes/1156?nv=1
History:
v1 (2026-05-12) (Latest)
0
Views
0
Calls
## 처음 Rust를 만났을 때 대부분의 개발자가 Rust를 처음 접하면 컴파일 오류에서 막힌다. "이미 move됐다", "라이프타임이 맞지 않는다". C++에서 넘어온 사람은 특히 좌절한다. 이런 건 런타임에 알아서 처리되던 것들이었으니까. 하지만 이 까다로움에는 명확한 이유가 있다. Rust의 borrow checker는 **소유권(ownership), 대여(borrowing), 라이프타임(lifetime)** 세 가지 규칙의 컴파일 타임 집행자다. ## 소유권 규칙 3가지 1. 각 값은 정확히 하나의 소유자(owner)를 가진다 2. 소유자가 스코프를 벗어나면 값이 drop된다 3. 값을 다른 변수에 할당하면 소유권이 이동(move)한다 — 원래 변수는 무효 ```rust let s1 = String::from("hello"); let s2 = s1; // move: s1은 이제 무효 // println!("{}", s1); // 컴파일 오류 ``` ## 왜 move인가 — double-free 방지 `s1`과 `s2`가 같은 힙 메모리를 가리키면, 둘 다 스코프를 벗어날 때 같은 메모리를 두 번 해제(double-free)하게 된다. Rust는 이것을 컴파일 타임에 막는다. GC가 필요 없다. ## 빌림 규칙 소유권을 이전하지 않고 참조를 빌릴 수 있다. 규칙: - 불변 참조(`&T`)는 동시에 여러 개 가능 - 가변 참조(`&mut T`)는 동시에 정확히 하나만 가능 - 불변과 가변을 동시에 가질 수 없다 ```rust let mut s = String::from("hello"); let r1 = &s; // OK let r2 = &s; // OK: 불변 참조 여러 개 허용 // let r3 = &mut s; // 오류: 불변 참조가 살아있는 동안 가변 불가 println!("{} {}", r1, r2); let r3 = &mut s; // OK: r1, r2의 마지막 사용이 끝났으므로 ``` ## 라이프타임이 필요한 이유 함수가 참조를 반환할 때, 반환된 참조가 어떤 입력 참조와 연결되어 있는지 컴파일러에게 알려줘야 한다. ```rust // 컴파일 오류: 반환값의 라이프타임 불명확 fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } // 올바른 표현: 반환값은 x나 y 중 더 짧은 수명을 가짐 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } ``` 라이프타임 어노테이션은 수명을 바꾸는 것이 아니다. 컴파일러에게 "이 참조들 사이의 관계"를 알려주는 문서다. ## 실전: borrow checker와 싸우지 않는 법 1. **clone() 남발은 학습 단계에서만** — 동작 이해 후에는 소유권 구조를 재설계 2. **Rc<RefCell<T>>는 최후의 수단** — 런타임 borrow checking으로 탈출구이지만 패닉 가능 3. **Arc<Mutex<T>>는 멀티스레드 공유** — 성능 비용 인지 후 사용 4. **구조체 분리로 해결** — 같은 구조체의 두 필드를 동시에 가변 빌림하려면 중간 구조체로 분리 ```rust // 문제: game.player와 game.enemies를 동시에 &mut // 해결: 중간 분리 let (player, enemies) = (&mut game.player, &mut game.enemies); // 또는 split_at_mut() 패턴 ``` ## Rust가 가르쳐주는 것 borrow checker를 통과한 코드는 데이터 레이스, use-after-free, null 역참조가 없다. 이건 보장이다. 보장을 위해 치르는 대가가 컴파일 타임 오류다. C++이나 Java에서 런타임에 발견하던 버그를 컴파일 타임으로 당긴 것이다. 까다롭다고 느끼는 건 사실이다. 하지만 그 까다로움이 정확히 "과거에 디버거로 며칠을 보내던 버그들"을 막고 있다.
// COMMENTS
Newest First
ON THIS PAGE