borrow checker가 `&mut`의 수명을 결정하는 방법

발행: (2026년 1월 16일 오전 04:54 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

소개

이 글에서는 borrow checkerNon‑Lexical Lifetimes (NLL) 환경에서 가변 참조(&mut)의 실제 수명을 어떻게 결정하는지 분석합니다.

특히, 대여의 수명이 어휘적 수명과 일치하지 않는 이유와, 흐름 제어 내에서 이후에 사용이 존재할 경우 명시적인 drop() 호출이 대여의 종료를 수동으로 강제할 수 없는 이유를 살펴보겠습니다.

Source: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
Source: https://rustlings.rust-lang.org/

기술 문제

Rust Book4.1 – What is Ownership 장과 Rustlings06_move_semantics/move_semantics4.rs 연습을 공부하면서, x에 대해 두 개의 가변 대여(&mut)를 연속으로 만들려는 겉보기에는 간단한 프로그램이 E0499 오류와 함께 실패합니다.

컴파일러는 다음과 같이 보고합니다:

A variable was borrowed as mutable more than once.

Erroneous code example:

let mut i = 0;
let mut x = &mut i;
let mut a = &mut i;
x;
// error: cannot borrow `i` as mutable more than once at a time

Please note that in Rust, you can either have many immutable references, or one mutable reference.

문제가 되는 코드는 다음과 같습니다:

#[cfg(test)]
mod tests {
    // TODO: Fix the compiler errors only by reordering the lines in the test.
    // Don't add, change or remove any line.
    #[test]
    fn move_semantics4() {
        let mut x = Vec::new();
        let y = &mut x;
        let z = &mut x;
        y.push(42);
        z.push(13);
        assert_eq!(x, [42, 13]);
    }
}

보시다시피, 첫 번째 대여가 아직(간접적으로) 필요함에도 불구하고 두 번째 &mut x가 시도됩니다.

Rust의 소유권 규칙은 한 번에 하나의 가변 참조(&mut)만 활성화될 수 있도록 강제합니다.

해결 방법

해결은 간단합니다: 두 번째 대여를 첫 번째 대여의 마지막 사용 이후로 옮기면 됩니다.

#[cfg(test)]
mod tests {
    #[test]
    fn move_semantics4() {
        let mut x = Vec::new();
        let y = &mut x;
        y.push(42);
        let z = &mut x;
        z.push(13);
        assert_eq!(x, [42, 13]);
    }
}

왜 동작하는가

  1. let y = &mut x; – 컴파일러는 x에 대한 가변 대여 y가 존재한다는 것을 기록합니다.
  2. y.push(42); – 이것이 y의 마지막 사용입니다. 이 줄 이후에 컴파일러는 x에 대한 활성 가변 대여가 더 이상 존재하지 않는다고 판단하므로 let z = &mut x;가 유효해집니다.

이 동작은 비-레시컬 수명(NLL, Non‑Lexical Lifetimes) 덕분에 가능합니다: 가변 대여는 마지막 사용이 끝나는 시점에 종료되며, 반드시 블록 끝에 있을 필요는 없습니다.

질문

y에 대한 라이프타임을 명시적으로 선언해서 컴파일러가 NLL을 사용하지 않게 하고, 코드로 내가 y가 언제까지 지속되는지 알려줄 방법이 있나요?

답변

아니요. 지역 변수에 명시적인 라이프타임을 선언하여 borrow checker가 NLL을 무시하도록 강제할 수 없습니다.
라이프타임 매개변수는 타입, 함수 또는 구조체에 서명을 붙이는 데 사용되며, 함수 내부 바인딩의 어휘적 지속 시간을 직접 제어하는 데 사용되지 않습니다.

drop()와 대출의 수명

라이프타임을 생각할 때, drop()을 사용해 대출을 “마지막 사용”보다 먼저 종료할 수 있는지 시도해 볼 수 있습니다.

fn main() {
    let mut x = Vec::new();
    let y = &mut x;
    y.push(42);
    drop(y);
    let z = &mut x;   // ← 여기서 E0499: y의 대출이 이후 사용 때문에 아직 살아 있음
    y.push(43);       // ← 여기서 E0382: drop(y) 이후 y를 사용함
    z.push(13);
}

컴파일러는 두 개의 서로 다른 오류를 발생시킵니다:

  • E0499cannot borrow 'x' as mutable more than once at a time
    첫 번째 대출이 아직 살아 있는 상태에서 두 번째 &mut x가 선언되었습니다.
  • E0382use of moved value 'y'
    ydrop(y)에 의해 이동되었지만, 이후에 다시 사용되고 있습니다.

drop()이 가변 대출을 단축하지 못하는가

  • NLL(Non‑Lexical Lifetimes)은 MIR(Mid‑level Intermediate Representation)를 기반으로 하며, 제어 흐름 상에서 식별된 마지막 사용까지 대출의 라이프타임을 연장하는 라이브니스 분석을 사용합니다.
  • drop(y)는 단순히 **이동(move)**일 뿐이며, 바인딩 y를 더 이상 사용할 수 없게 만들지만, 컴파일러가 y의 이후 사용을 감지하면 대출 영역을 단축하지 않습니다.

실제로 drop(y)그 이후에 y가 전혀 사용되지 않을 때만 대출을 종료합니다. 이후에 사용이 있다면, NLL은 대출의 수명을 그 사용까지 역으로 연장하므로 drop은 대출 검사 관점에서 무시됩니다.

실용 원칙

첫 번째 &mut마지막 사용이 새로운 가변 대여를 만들기 앞에 발생하도록 확인하세요. 대여를 미리 “해제”해야 할 경우, 이후에 사용이 없도록 코드를 구조화하거나, 바인딩의 레키컬 범위를 제한하기 위해 블록({ … })을 사용해야 합니다.

NLL 덕분에 컴파일러가 이미 이 작업의 대부분을 대신 수행하지만, drop()이 라이프타임을 단축시키는 마법의 막대가 아니라는 점을 이해하는 것이 중요합니다.


명시적인 스코프, 함수로 추출하거나 데이터를 재설계하여 불필요한 가변 별칭을 방지함으로써 다음 &mut의 생성을 관리할 수 있습니다.

당신은요? 비슷한 경우를 겪어본 적이 있나요?

댓글로 알려 주세요!

곧 뵙겠습니다,

Mirko

Back to Blog

관련 글

더 보기 »

6년 이상 C# 사용 후 Rust로 전환 계획

저는 C로 6~7년 동안 작업해 왔지만, 시간이 지나면서 점점 부피가 크고 제한적인 느낌이 듭니다. 사물의 동작 방식을 정확하고 low‑level로 제어하던 것이 그리워집니다.