Rust에서 빌림은 소유권을 옮기지 않으면서도 데이터를 접근할 수 있게 해주는 핵심 개념이다. 데이터에 대한 접근 권한을 공유하는 방식에 따라 가변 참조와 불변 참조로 구분되며, 이는 Rust의 안전성과 동시에 효율적인 메모리 관리를 실현하는 토대가 된다.
소유권 규칙을 살펴보면 하나의 값에 대해 동시에 여러 개의 가변 참조를 갖는 것은 허용되지 않는다. 즉, 같은 스코프에서 특정 값에 대해 오직 하나의 가변 참조만이 존재할 수 있다. 반면, 불변 참조는 여러 개가 동시에 존재하더라도 상관없다. Rust가 이러한 규칙을 엄격히 적용하는 이유는 데이터 경합(data race)이나 메모리 안전성 문제가 발생하는 것을 원천적으로 방지하기 위해서다.
러닝 중 흔히 접하게 되는 에러 메시지 중 하나가 “cannot borrow x as mutable because it is also borrowed as immutable” 같은 형태인데, 이는 이미 불변 참조가 존재하는 상황에서 가변 참조를 추가하려고 시도했기 때문이라는 사실을 보여준다. 한편 가변 참조가 존재하는데 동시에 불변 참조를 생성하려 해도 마찬가지로 에러가 발생한다. Rust의 빌림 체계는 불변과 가변의 공존으로 발생할 수 있는 잠재적 문제를 제거하여, C/C++에서 흔히 마주칠 수 있는 미정의 동작(undefined behavior)을 컴파일 타임에 안전하게 막는다.
가변 참조는 해당 값의 내용을 변경하거나 수정해야 할 때 사용한다. 불변 참조는 소유권 없이 값의 내용을 읽고 싶을 때 사용한다. 다만, 빌림 기간 동안에는 참조 규칙을 반드시 지켜야 하며, 이를 어기는 코드는 컴파일 단계에서 거부당한다.
아래 예제 코드를 통해 이러한 가변 참조와 불변 참조의 제약을 직관적으로 살펴볼 수 있다.
fn main() {
let mut s = String::from("Hello");
let r1 = &s; // 불변 참조
let r2 = &s; // 불변 참조 추가
// let r3 = &mut s; // 가변 참조로 빌리려고 시도하면 컴파일 에러가 발생함
println!("{} and {}", r1, r2);
let r4 = &mut s; // 가변 참조
r4.push_str(", world!");
println!("{}", r4);
}
처음에 s에 대해 불변 참조가 r1과 r2로 생성된다. 이후 r1과 r2의 사용이 끝나기 전에 &mut s를 시도하면, 불변 참조가 유효 범위 내에 있기 때문에 가변 참조가 불가능하다는 에러가 발생한다. 하지만 r1과 r2가 실제로 더 이상 사용되지 않는 시점(println!으로 호출이 끝난 뒤)부터 러스트는 빌림이 종료된 것으로 간주하므로, 그 후에 가변 참조 r4를 생성해도 안전하다고 판단한다. 빌림 검사기( borrow checker)는 프로그램 전반의 스코프를 살피면서 언제 참조가 더 이상 유효하지 않은지를 추론한다.
Rust에서 가변 참조가 하나만 허용되는 까닭은 명시적인 동시성 모델 외에 암묵적인 동시성이나 멀티스레드 환경에서도 데이터 레이스를 일으킬 수 있는 여지를 사전에 차단하기 위함이다. 여러 개의 가변 참조가 동시에 같은 값을 수정할 수 있다면, 멀티스레드 환경에서 동기화 문제를 제대로 다루지 않는 한 무결성이 깨질 위험이 커진다. Rust는 이러한 상황을 근본적으로 없애며, 필요하다면 더 높은 수준의 동시성 추상화(예: Arc, Mutex)와 함께 사용자가 의도적으로 경쟁 상태를 다룰 수 있도록 설계되어 있다.
불변 참조는 여러 개 동시에 존재해도 문제가 없는 이유는 그 어떤 참조도 내부 데이터를 변경하지 않으므로, 읽기 중심의 접근만 일어나는 경우에는 충돌이 발생하지 않는다. 다만, 내부 가변성(Interior Mutability)을 활용하여 불변 참조로도 내부 데이터를 변경할 수 있는 UnsafeCell이나 RefCell 같은 타입이 존재하기 때문에, 이러한 경우에는 추가적인 주의가 필요하다. 일반적으로는 불변 참조와 가변 참조의 구분이 명확하여 빌림 규칙을 체계적으로 적용할 수 있다.
가변 참조와 불변 참조의 빌림 규칙은 러스트의 메모리 안전성을 대표하는 기법이다. 소유권을 명확히 하고 빌림 시점에 대한 접근 권한을 엄격히 제어함으로써, 대규모 프로젝트나 시스템 프로그래밍 환경에서도 런타임에 발생하기 어려운 메모리 오류를 컴파일 단계에서 원천적으로 차단한다. 이러한 특징은 러스트가 안전성과 성능을 동시에 추구할 수 있는 기반이라 할 수 있다.
가변 참조와 불변 참조를 적절히 활용하면 불필요한 복사나 소유권 이동 없이도 데이터를 자유롭게 재사용하고 수정할 수 있다. 더 나아가 멀티스레드 프로그래밍에서 잠재적 경쟁 상태를 해소하고자 할 때, Rust는 이러한 빌림 모델을 토대로 안전성을 강조하는 다양한 동시성 도구를 제시한다. 이러한 점에서 가변 참조와 불변 참조의 구분은 러스트 특유의 엄격하지만 효율적인 프로그래밍 경험을 제공한다.