대여 알아보기

완료됨

이전 모듈에서는 값에 소유자를 포함하는 방법을 알아보았습니다. 한 변수에서 다른 변수로 소유권을 이동하여 값의 소유권을 이전합니다. 숫자 같은 단순 값의 경우와 같이 Copy 특성을 구현하는 형식은 소유권을 이전할 수 없습니다.

‘복제’ 프로세스를 사용하여 값을 ‘명시적으로’ 복사할 수도 있습니다. clone 메서드를 호출하고 복사되는 새 값을 가져옵니다. 그러면 원래 값이 이동되지 않고 계속 사용할 수 있습니다.

함수 및 기타 변수가 완전히 소유하지 않고 특정 데이터를 사용하도록 허용할 수 있다면 좋지 않을까요?

이 유형의 기능은 ‘참조’를 사용하여 사용할 수 있습니다. 참조를 사용하면 소유권을 차지하지 않고 값을 “대여”할 수 있습니다.

let greeting = String::from("hello");
let greeting_reference = &greeting; // We borrow `greeting` but the string data is still owned by `greeting`
println!("Greeting: {}", greeting); // We can still use `greeting`

이전 코드에서는 참조 기호(&)를 사용하여 greeting을 빌렸습니다. 변수 greeting_reference가 문자열 참조(&String) 형식이었습니다. greeting을 대여만 하고 소유권을 이동하지 않았기 때문에 greeting_reference를 만든 후에도 greeting을 계속 사용할 수 있었습니다.

함수의 참조

실제로 greeting_reference를 아무것에도 사용하지 않으므로 이 예제는 그리 흥미롭지 않습니다. 함수에서 참조를 사용하는 방법을 살펴봅시다.

fn print_greeting(message: &String) {
  println!("Greeting: {}", message);
}

fn main() {
  let greeting = String::from("Hello");
  print_greeting(&greeting); // `print_greeting` takes a `&String` not an owned `String` so we borrow `greeting` with `&`
  print_greeting(&greeting); // Since `greeting` didn't move into `print_greeting` we can use it again
}

대여를 사용하면 전체 소유권을 차지하지 않고 값을 사용할 수 있습니다. 그러나 나중에 확인하겠지만, 값을 대여하는 경우 완전히 소유한 값으로 수행할 수 있는 모든 작업이 가능한 것은 아닙니다.

대여한 값 변경

대여한 값을 변경하려고 하면 어떻게 될까요?

fn change(message: &String) {
  message.push_str("!"); // We try to add a "!" to the end of our message
}

fn main() {
  let greeting = String::from("Hello");
  change(&greeting); 
}

이 코드는 컴파일되지 않습니다. 대신 다음과 같은 컴파일러 오류가 표시됩니다.

error[E0596]: cannot borrow `*message` as mutable, as it is behind a `&` reference
 --> src/main.rs:2:3
  |
1 | fn change(message: &String) {
  |                    ------- help: consider changing this to be a mutable reference: `&mut String`
2 |   message.push_str("!"); // We try to add a "!" to the end of our message
  |   ^^^^^^^ `message` is a `&` reference, so the data it refers to cannot be borrowed as mutable

컴파일러 오류를 자세히 살펴보면 형식 매개 변수를 &String에서 &mut String으로 변경하여 참조를 ‘변경 가능’한 것으로 변경하는 방법에 관한 힌트를 확인할 수 있습니다. 또한 원래 값을 변경 가능으로 선언해야 합니다.

fn main() {
    let mut greeting = String::from("hello");
    change(&mut greeting);
}

fn change(text: &mut String) {
    text.push_str(", world");
}

‘변경 불가능 대여’라고 하는 & 대여를 사용하면 데이터를 읽을 수만 있고 변경할 수는 없습니다. "변경 가능 대여"라고 하는 &mut 대여를 사용하면 데이터를 읽고 변경할 수 있습니다.

대여 및 변경 가능 참조

이제 Rust의 메모리 관리 스토리의 핵심에 도달했습니다. 변경 불가능 참조와 변경 가능 참조 간에는 Rust 프로그램을 빌드하는 방법에 근본적인 영향을 주는 또 다른 차이점이 있습니다. T 형식의 값을 대여하는 경우 다음 규칙이 적용됩니다.

코드는 동시에 둘 다가 아니라 다음 정의 중 ‘하나’를 구현해야 합니다.

  • 하나 이상의 변경 불가능 참조(&T)
  • 정확히 하나의 변경 가능 참조(&mut T)

다음 코드에는 허용되는 정의가 없으므로 컴파일이 실패합니다.

fn main() {
    let mut value = String::from("hello");

    let ref1 = &mut value;
    let ref2 = &mut value;

    println!("{}, {}", ref1, ref2);
}
    error[E0499]: cannot borrow `value` as mutable more than once at a time
     --> src/main.rs:5:16
      |
    4 |     let ref1 = &mut value;
      |                ---------- first mutable borrow occurs here
    5 |     let ref2 = &mut value;
      |                ^^^^^^^^^^ second mutable borrow occurs here
    6 |
    7 |     println!("{}, {}", ref1, ref2);
      |                        ---- first borrow later used here

변경 불가능 참조와 변경 가능 참조를 혼합하려 할 수는 있지만 컴파일러에서 경고할 것입니다.

fn main() {
    let mut value = String::from("hello");

    let ref1 = &value;
    let ref2 = &mut value;

    println!("{}, {}", ref1, ref2);
}
    error[E0502]: cannot borrow `value` as mutable because it is also borrowed as immutable
     --> src/main.rs:5:16
      |
    4 |     let ref1 = &value;
      |                ------ immutable borrow occurs here
    5 |     let ref2 = &mut value;
      |                ^^^^^^^^^^ mutable borrow occurs here
    6 |
    7 |     println!("{}, {}", ref1, ref2);
      |                        ---- immutable borrow later used here

처음에는 제한이 너무 엄격해 보일 수도 있지만 이 대여 양상으로 인해 Rust 코드에서 데이터 경합이 발생하지 않는 등 모든 문제가 방지됩니다.