----------------
title : Rust 의 레퍼런스들에 관한 규칙들
cat_title : Rust 의 레퍼런스들에 관한 규칙들
path : /Rust
publish_date : 2023-09-08
--------------

이 글은 Rust 에서 레퍼런스들에 대해서 공부하면서 머리를 싸메던 저에게 한 줄기 빛과 같았던 [글](https://mechpen.github.io/posts/2019-10-20-rust-lifetime-mut/) 을 읽고 제 나름대로 다시 정리하여서 쓴 글입니다.

Rust 에선 두 가지 종류의 레퍼런스를 정의할 수 있습니다. 하나는 참조하는 변수를 변경할 수 있는 변경 가능한(mutable) 레퍼런스고, 다른 하나는 참조하는 대상을 변경할 수 없는 변경 불가능(immutable) 한 레퍼런스 입니다. C++ 에서 오신 분들은 레퍼런스와 `const` 레퍼런스로 생각하시면 됩니다. 

하지, Rust 의 레퍼런스와 C++ 의 레퍼런스는 큰 차이가 있는데, C++ 의 경우 아무런 제약 없이 어떤 변수에 대해서 임의로 *const/non-const* 레퍼런스들을 마음껏 정의하고 사용할 수 있지만, Rust 에선 *mutable/immutable* 레퍼런스를 사용하기 위해서는 몇 가지 중요한 규칙들을 지켜야 합니다. 이 글에서는 이 규칙들에 대해서 간단히 이야기 하고자 합니다.

Rust 에서 변경 가능한 레퍼런스를 만들기 위해서는 `mut` 키워드를 레퍼런스를 정의하면 됩니다. 예를 들어서

```rust
let mut x = 3;
let mrx = &mut x; // x 에 대한 mutable reference.
*mrx = 5;
println("{}", x); // '5'
```


위 처럼 `&mut` 키워드로 정의된 레퍼런스인 `mrx` 를 통해서 `x` 의 값을 변경할 수 있습니다.

반면에 아무 키워드 없이 정의된 레퍼런스는 참조하는 값을 변경할 수 없고 *읽기* 만 가능합니다.

```rust
let mut x = 3;
let rx = &x; // x 에 대한 immutable reference.
// *rx = 5; 불가능
println("{}", *rx); // '3'
```

각각의 레퍼런스를 언제 사용할 수 있는지는 아래 두 개의 규칙에 따라 작동합니다.

## 변경 불가능한 레퍼런스에 관한 규칙

**어떤 변수의 immutable 레퍼런스의 라이프타임 안에서 해당 변수는 immutable 하고, immutable 한 레퍼런스만 만들 수 있다.**

예를 들어서 아래와 같은 코드를 살펴봅시다.


```rust
struct S{}

fn main() {
    let mut x = S{};      
    let rx = &x;          // ---- rx 의 라이프타임 시작 
    &x;                   // 가능. x 의 immutable 한 레퍼런스 만들 수 있음.
    // x = S{};           // 불가능. x 는 immutable 하기 때문
    // let y = x;         // 불가능. 역시 x 는 immutable 하기 때문.
    // x;                 // 불가능. 암시적인 move.
    // let mrx = &mut x;  // 불가능. immutable 한 레퍼런스만 만들 수 있음.
    rx;                   // ---- rx 의 라이프타임 끝.
}
```

여기서 중요한 점은 위 규칙은 **immutable 레퍼런스의 라이프타임 안에서만** 적용된다는 점입니다. 예를 들어서 아래 코드를 봅시다.

```rust
struct S{}

fn main() {
    let mut x = S{};      
    let rx = &x;          // ---- rx 의 라이프타임 시작 

    // 만일 밑에서 rx 가 쓰이지 않는다면 rx 의 라이프타임은 여기서 끝난다.

    let mrx = &mut x;  // 가능. rx 의 라이프타임 밖이므로!
}
```

위 경우 `mrx` 밑으로 `rx` 가 쓰이지 않기 때문에 `rx` 의 라이프타임은 `mrx` 바로 위에서 끝나게 됩니다. 따라서 위 규칙을 위반하지 않고 `x` 의 mutable 레퍼런스인 `mrx` 를 정의할 수 있겠죠.

한 가지 중요한 점은 **라이프타임 이란 것이 연속적인 영역이 아니라는** 점입니다.

```rust
fn main() {
    let mut x = 10;
    let rx = &x; // rx 의 라이프타임

    if rand() > 1 { // (1)
        // rx 의 라이프타임 아님.
        let mrx = &mut x; 
        *mrx = 11;
    } else { // (2)
        println!("{}", rx); // rx 의 라이프타임
    }
}
```

위 경우, x 의 mutable 레퍼런스인 `mrx` 가 `rx` 밑에 정의되었지만 위 코드는 성공적으로 컴파일 됩니다. 왜냐하면 컴파일러가 (1) 번 `if` 문 branch 를 택하게 되면 `rx` 가 더이상 쓰이지 않는다는 것을 알기 때문에 해당 영역을 `rx` 의 라이프타임에서 제외할 수 있습니다. 따라서 규칙을 위반하지 않고 `x` 의 mutable 레퍼런스를 정의할 수 있죠. 

## 변경 가능한 레퍼런스에 관한 규칙

**Mutable 레퍼런스를 정의하는 것은 참조하는 변수를 해당 레퍼런스의 라이프타임 동안 임시적으로 이동시킨 것과 같다.**

변경 가능한 레퍼런스를 정의하는 것은 해당 변수를 그 레퍼런스로 잠시 동안 이동시킨 (move) 것과 같다고 생각하면 됩니다. 그리고, 레퍼런스의 라이프타임이 끝나게 되면 다시 레퍼런스에서 원래 변수로 돌아가게 됩니다. 예를 들어서 

```rust
fn main() {
    let mut x = 1;
    let rx = &x;
    let mrx = &mut x;     // x 가 *mrx 로 이동했다고 보면 됩니다.
    // x;                 // 현재 x 가 mrx 로 이동된 상태이므로 x 를 사용할 수 없습니다.
    // rx;                // 여기서 rx 를 사용하게 되면 rx 의 라이프타임이 여기 까지 연장됩니다.
                          // 그러면 immutable 레퍼런스 규칙에 위배되죠 (mutable 레퍼런스가 정의되어 있으므로)
    mrx;                  // 이 다음 부터 mrx 의 라이프타임이 끝납니다.
    x;                    // 다시 x 를 사용할 수 있습니다.
}
```

Mutable 레퍼런스 `&mut T` 는 `Copy` 를 구현하고 있지 않습니다. 따라서, 아래와 같은 `=` 는 기본적으로 move 로 구현됩니다. 

```rust
fn main() {
    let mut x = 1;
    let mrx = &mut x;
    let mry = mrx;    // mrx 를 mry 로 이동.
    // mrx;           // 따라서 mrx 를 사용할 수 없다.
}
```

만일 `mrx` 를 사용하려고 한다면 아래와 예상했던 대로 이동된 값을 참조한다는 오류가 발생하게 됩니다. 

```error
3 |     let mrx = &mut x;
  |         --- move occurs because `mrx` has type `&mut i32`, which does not implement the `Copy` trait
4 |     let mry = mrx;            // mrx 를 mry 로 이동.
  |               --- value moved here
```

### 다시 빌리기 (Reborrow)

그런데 mutable 레퍼런스가 다른 mutable 레퍼런스로 전달 될 때 항상 이동만 되는 것은 아닙니다. 아래와 같은 코드를 생각해봅시다.

```rust
fn take_mut(d: &mut i32) {
    *d += 1;
}

fn main() {
    let mut x = 1;

    let mrx = &mut x;
    take_mut(mrx); // mrx 가 d 로 이동이 되나?
    take_mut(mrx); // 만일 이동이 되었다면 mrx 를 사용할 수 없는데..

    println!("{}", x);
}
```

실행 해본다면 놀랍게도 컴파일 오류 없이

```exec
3
```

제대로 `take_mut` 이 `mrx` 를 받아서 여러번 호출된 것을 확인할 수 있습니다. 만일 mutable 레퍼런스가 항상 이동만 되었다면 첫 번째 `take_mut` 호출에서 `mrx` 가 `take_mut` 의 인자인 `d` 로 *이동* 되었을 것입니다. 그렇다면 `take_mut` 이후에서 `mrx` 를 사용할 수 없겠지요. 하지만 그 뒤에도 다시 `take_mut` 을 호출할 수 있었습니다.

이게 가능한 이유는 mutable 레퍼런스를 전달하는 두 번째 방식인 **다시 빌리기 (Reborrow) 때문입니다**. 위 경우 `d` 는 `*mrx` 를 **다시 빌리게 되고**, `d` 의 라이프타임이 끝나게 되면 `x` 의 소유권이 다시 원래 주인이였던 `mrx` 로 돌아가게 됩니다. 

Rust 컴파일러는 어떤 mutable 레퍼런스가, 다른 **mutable 레퍼런스로 정의된** 레퍼런스로 전달이 된다면 mutable 레퍼런스를 이동 시키는 대신에 *reborrow* 를 수행합니다. 여기서 중요한 점은 다른 레퍼런스가 **명시적으로** mutable 로 정의 되어 있어야 한다는 것입니다.

예를 들어서, 위 move 예제에서 `mry` 는 `&mut i32` 로 명시적으로 타입이 정의되어 있지 않기 때문에 컴파일러가 그냥 이동을 수행하지만, `take_mut` 함수에서 `d` 는 `&mut i32` 로 명시적으로 mutable 레퍼런스로 정의되어 있기 때문에 reborrow 가 수행이 되는 것입니다.

즉, `take_mut(mrx)` 는

```rust
take_mut(&mut *mrx);
```

로 변환되서 컴파일 됩니다. 기존 레퍼런스 (`mrx`) 가 역참조(dereference) 되고, 다시 그 값에 대한 새로운 mutable 레퍼런스가 생성이 되서 함수에 전달됩니다. 그리고 함수가 끝나게 되면 다시 소유권이 `mrx` 로 넘어오게 되죠.

만일 위 이동 예제에서 `mry` 가 `mrx` 를 역참조 하게 된다면

```rust
fn main() {
    let mut x = 1;
    let mrx = &mut x;
    let mry = &mut *mrx; // mry 가 mrx 를 reborrow
    // let mry: &mut i32 = mrx; 도 됩니다.

    // mry 의 라이프타임 끝.

    mrx; // 따라서 mrx 를 다시 사용할 수 없다.
}
```

위 처럼 `mry` 의 라이프타임이 끝났을 때 `mrx` 를 다시 사용할 수 있게 됩니다.

위 기본적인 규칙들만 잘 기억을 한다면 Rust 에서 레퍼런스를 다룰 때 아마 큰 도움이 될 것이라 생각합니다. 그러면 오늘도 Rust 컴파일러와 고군분투를 벌이고 있는 여러분들을 응원하며 이 글을 마치도록 하겠습니다 :)

### 참고자료

다음 글들이 큰 도움이 되었습니다.

* [Understanding Rust lifetime and mutability](https://mechpen.github.io/posts/2019-10-20-rust-lifetime-mut/)
* [How can I reborrow a mutable reference without passing it to a function?](https://stackoverflow.com/questions/43036156/how-can-i-reborrow-a-mutable-reference-without-passing-it-to-a-function)
* [Do mutable references have move semantics?](https://stackoverflow.com/questions/62960584/do-mutable-references-have-move-semantics/62961289#62961289)
* [Variable binding: moving a &mut or borrowing the referent?](https://stackoverflow.com/questions/30535529/variable-binding-moving-a-mut-or-borrowing-the-referent)
* [How does rust pass or bind to a mutable reference](https://users.rust-lang.org/t/how-does-rust-pass-or-bind-to-a-mutable-reference/71614)
* [Reborrowing 에 관련한 Github 이슈](https://github.com/rust-lang/reference/issues/788)