소유권 시스템(ownership)은 러스트의 메모리 안전성을 컴파일 수준에서 보장하는 기본 개념입니다. 각 변수는 소유자를 가지고 있습니다. 특정 시점에 자원의 소유자는 오직 하나만 존재할 수 있습니다.
소유권은 할당하거나 함수에 전달할 때 전송(move)됩니다. 이전 소유자가 값을 사용할 수 없게 됩니다:
let s = String::from("hello"); let t = s; // s는 더 이상 사용할 수 없음
러스트는 빌림(borrow)도 구별합니다.
&T — 읽기 전용 참조.&mut T — 가변 참조, 그러나 동시에 하나만 존재할 수 있습니다.이 규칙들 덕분에 러스트는 메모리가 해제된 후에 사용되는 데이터 레이스와 기타 자원 관리 오류가 발생하지 않도록 보장합니다.
하나의 값에 대해 동일하게 여러 개의 가변 참조(&mut T)가 동시에 있을 수 있나요? 왜요?
답: 아닙니다, 어느 순간에 불변 참조는 무수히 많을 수 있지만, 가변 참조는 오직 하나만 존재할 수 있습니다. 이는 데이터 레이스를 방지합니다. 잘못된 코드 예:
let mut s = String::from("hi"); let r1 = &mut s; let r2 = &mut s; // 컴파일 오류!
이야기
하나의 멀티스레드 프로젝트에서 개발자는 소유권을 사용하지 않고 가변 버퍼에 대한 참조를 저장하려고 하였고, 이로 인해 버퍼가 해제된 후에 사용되는 오류(use-after-free)가 발생했습니다. 러스트는 이러한 코드를 컴파일할 수 없도록 하였고, 결국 아키텍처를 변경해야 했습니다.
이야기
큰 러스트 프로젝트의 시작에서 프로그래머들은 C++에서 코드를 아카이브하였습니다. 그들은 구식의 "원시" 포인터 작업 방식을 유지하려 했고, 이는 자원 소유와 관련한 오류와 borrow checker의 지속적인 패닉을 초래했습니다. 결국 소유권 아키텍처 문제를 해결해야 했습니다.
이야기
문자열 파서 라이브러리에서는 러스트의 엄격한 소유권 시스템 덕분에 이중 해제 오류를 피할 수 있었습니다. C++의 유사 라이브러리는 찾기 어려운 버그와 메모리 누수를 초래했습니다.