[Chapter4-2] Rust 소유권(Ownership) 02 - 참조자와 빌림

hwwwa·2021년 10월 28일
0

🦀 Rust

목록 보기
9/25
post-thumbnail
post-custom-banner

참조자(References)와 빌림(Borrowing)

함수에게 값을 사용할 수 있도록 하되 소유권은 갖지 않도록 하고싶다면 참조자(reference) 를 사용할 수 있습니다.

아래의 코드는 소유권을 넘기는 대신 개체에 대한 참조자를 인자로 사용하는 calculate_length 함수를 정의합니다.

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

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

calculate_length 함수에 &s1를 넘기고, 함수 정의 부분에는 &String을 이용했습니다. & 기호가 참조자이며, 이는 어떤 값을 소유권을 넘기지 않고 참조할 수 있도록 해줍니다.

&s1 문법은  s1의 값을 참조하지만 소유하지않는 참조자를 생성하도록 해줍니다. 소유권을 갖지 않기 때문에 참조자가 가리키는 값은 참조자가 스코프 밖으로 벗어나도 메모리 반납이 일어나지 않습니다.

비슷하게 함수 시그니처도 & 를 사용해 인자 s 의 타입이 참조자라는 것을 나타내고 있습니다.

fn calculate_length(s: &String) -> usize {// s는 String의 참조자입니다
    s.len()
}// 여기서 s는 스코프 밖으로 벗어났습니다.
 // 하지만 가리키고 있는 값에 대한 소유권이 없기 때문에, 아무런 일도 발생하지 않습니다.

함수의 파라미터로 참조자를 만드는 것을 빌림 이라고 부릅니다. 만약 빌린 값을 고치려고 한다면 에러가 발생할 것입니다. 변수가 기본적으로 불변인 것처럼, 참조자도 불변입니다. 참조하는 어떤 것을 변경하는 것은 허용되지 않습니다.

가변 참조자(Mutable References)

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

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

&mut s로 가변 참조자를 생성할 수 있으며 some_string: &mut String 으로 가변 참조자를 받아야 합니다.

하지만 특정한 스코프 내 특정한 데이터 조각에 대한 가변 참조자를 딱 하나만 만들 수 있다는 큰 제한이 있습니다.

let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;

위 코드 실행 시 에러가 발생하는 것을 볼 수 있습니다.

error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> borrow_twice.rs:5:19
  |
4 |     let r1 = &mut s;
  |                   - first mutable borrow occurs here
5 |     let r2 = &mut s;
  |                   ^ second mutable borrow occurs here
6 | }
  | - first borrow ends here

Rust는 가변을 허용하긴 하지만 매우 통제된 형식으로 허용합니다. 이는 Rust가 컴파일 타임에 data race를 방지할 수 있도록 해준다는 장점을 가집니다.

data race 는 아래의 세 가지 동작이 발생했을 때 나타나는 특정한 race 조건입니다.

  1. 두 개 이상의 포인터가 동시에 같은 데이터에 접근
  2. 그 중 적어도 하나의 포인터가 데이터를 사용
  3. 데이터에 접근하는 데 동기화를 하는 어떠한 매커니즘도 없음

data race는 정의되지 않은 동작을 일으키고 런타임에 이를 추적하고자 할 때 진단하고 고치기 어려울 수 있습니다. Rust는 data race가 발생할 수 있는 코드가 컴파일 조차 가능하지 않기 때문에 문제의 발생을 막아줍니다.

새로운 스코프를 위한 중괄호를 사용은 동시에 가변 참조자를 생성하지 않도록 해줍니다. 따라서 중괄호의 사용을 통해 여러 개의 가변 참조자를 만드는 것이 가능합니다.

let mut s = String::from("hello");

{
    let r1 = &mut s;
    
} // 여기서 r1은 스코프 밖으로 벗어났으므로, 우리는 아무 문제 없이 새로운 참조자를 만들 수 있습니다.

let r2 = &mut s;

불변 참조자를 가지고 있는 동안에는 가변 참조자를 만들 수 없습니다. 아래의 코드는 컴파일 오류가 발생합니다.

let mut s = String::from("hello");

let r1 = &s; // 문제 없음
let r2 = &s; // 문제 없음
let r3 = &mut s; // 큰 문제
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable
 --> borrow_thrice.rs:6:19
  |
4 |     let r1 = &s; // 문제 없음
  |               - immutable borrow occurs here
5 |     let r2 = &s; // 문제 없음
6 |     let r3 = &mut s; // 큰 문제
  |                   ^ mutable borrow occurs here
7 | }
  | - immutable borrow ends here

하지만 여러 개의 불변 참조자 생성은 가능합니다. 데이터를 읽기만 하는 것은 다른 불변 참조자가 그 데이터를 읽는 데에 어떠한 영향도 주지 않기 때문입니다.

댕글링 참조자 (Dangling References)

댕글링 포인터란 어떤 메모리가 가리키는 포인터를 보존하는 동안 그 메모리를 해제함으로써 다른 개체가 사용하고 있을지도 모를 메모리를 참조하고 있는 포인터를 뜻합니다. Rust에서는 컴파일러가 모든 참조자들이 댕글링 참조자가 되지 않도록 보장해줍니다. 만일 어떤 데이터의 참조자를 만들었다면, 컴파일러는 그 참조자가 스코프 밖으로 벗어나기 전에는 데이터가 스코프 밖으로 벗어나지 않을 것임을 확인해줍니다.

아래 코드는 댕글링 참조자를 만드려고 하는 코드입니다.

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String { // dangle은 String의 참조자를 반환합니다

    let s = String::from("hello"); // s는 새로운 String입니다

    &s // 우리는 String s의 참조자를 반환합니다.
} // 여기서 s는 스코프를 벗어나고 버려집니다. 이것의 메모리는 사라집니다.
  // 위험하군요!
error[E0106]: missing lifetime specifier
 --> dangle.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^^^^^^^
  |
  = help: this function's return type contains a borrowed value, but there is no
    value for it to be borrowed from
  = help: consider giving it a 'static lifetime

error: aborting due to previous error

오류 메세지는 "이 함수의 반환 타입은 빌린 값을 포함하고 있는데, 빌려온 실제 값은 없습니다"라고 알려줍니다.

위의 코드에서 dangle 함수 안에서 String 의 참조자를 반환하는 것이 아닌 String 을 직접 반환하면 오류가 해결됩니다. 소유권이 밖으로 이동되고 아무것도 할당 해제되지 않습니다.

참조자의 규칙

앞서 다룬 내용을 요약하자면,

  1. 어떠한 경우이든 간에 아래의 두 경우 중 하나만 가질 수 있습니다.
    • 하나의 가변 참조자
    • n개의 불변 참조자
  2. 참조자는 항상 유효해야만 합니다.
post-custom-banner

0개의 댓글