체인의정석

Rust Ownership - Reference and Borrowing 본문

블록체인/Rust

Rust Ownership - Reference and Borrowing

체인의정석 2023. 1. 19. 10:17
728x90
반응형

References와 Borrowing

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을 calculate_length로 넘기고 정의를 할 때 &String을 String 대신에 가져간다.

그림 1

위의 그림 1은 &String이 String1에 포인터를 넘겨받아서 가지고 있는 상황을 의미한다.

(s가 함수의 인자값인 상황에서 원래 s1이 스택에 들어가 있는데 이를 s가 포인터만 가지고 한번 더 연결을 해서 기존의 값에는 영향을 안 끼치는 형태로 추가가 되는 것이다.)

 

    let s1 = String::from("hello");

    let len = calculate_length(&s1);

이 부분을 보면 &s1의 문법은 s1에 대한 값을 refer 해주지만 소유하지는 않는다. 소유를 하지 않기 때문에 사용이 되지 않더라도 값 또한 drop 되지 않습니다. 비슷하게 함수의 signature는 &를 사용하는데 

fn calculate_length(s: &String) -> usize { // s is a reference to a String
    s.len()
} // Here, s goes out of scope. But because it does not have ownership of what
  // it refers to, it is not dropped.

이런 식으로 &를 통해서 받은 경우 스코프 밖으로 가도 drop이 일어나지 않는다.

이렇게 reference를 만드는 행동의 borrowing이라고 한다.

 

만약 borrowing을 한 상황에서 무언가 변경하려고 한다면 에러가 나게 된다.

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

    change(&s);
}

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

예를 들어 이런식으로 &로 참조값을 가져온 후에 (빌린 후에) 수정하려할 시에는

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- help: consider changing this to be a mutable reference: `&mut String`
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable

For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` due to previous error

컴파일 시에 cannot borrow라는 문구와 함께 ownership관련 에러가 나게 된다.

 

가변 참조 Mutable References

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

    change(&mut s);
}

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

위와 같은 함수에서 s를 mut으로 바꾸게 된다면 chage함수는 빌려온 값을 변화시키게 된다.

가변 참조에서는 하나의 큰 제약이 붙는다. 만약 가변 참조 변수가 값을 가진다면 해당 값은 다른 참조값이 없어지게 된다. 

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

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

    println!("{}, {}", r1, r2);
}

이런식으로 2개의 가변 참조 값을 가져오는 경우에는 실패를 하게 된다.

이러한 제약 조건은 같은 데이터가 같은 시간에 다양한 컨트롤이 되지 않도록 막아주는 역할을 한다.

 

이를 통해 data race라는 것을 막아준다.

data race는 위의 3가지 일들이 일어날 때 발생한다.

1. 2개 이상의 포인터가 같은 데이터에 접근한다.

2. 최소 하나의 포인터가 데이터를 작성하는데 사용되고 있다.

3. 데이터 접근에 대한 통일성을 주는 메커니즘이 없다.

 

러스트는 이런 data races 상황(진단이 어렵고 실행시에 수정이 어려운 상황)

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

    {
        let r1 = &mut s;
    } // r1 goes out of scope here, so we can make a new reference with no problems.

    let r2 = &mut s;
}

이에 따라 같은 가변 참조값을 여러번 사용하고 싶다면 대괄호를 써서 사용할 수 있다.

 

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

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

    println!("{}, {}, and {}", r1, r2, r3);
}

또한 러스트에서는 다음과 같이 mut가 들어간 레퍼런스와 안들어간 레퍼런스를 동시에 사용할 경우에도 에러가 발생하게 된다.

이런식으로 컴파일 시에는 에러가 자주 발생하지만 그만큼 러스트는 런타임 에러는 적게 발생하게 된다.

 

Dangling Reference

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

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

dangling 포인터는 메모리에서 다른 사람에게 준 위치를 알려주는 포인터인데 실수로 만들기가 쉽다. 메모리는 지웠지만 포인터는 남겨진 상황이다. 러스트에서는 컴파일러가 이러한 danglind Reference가 발생하지 않도록 막아준다. 스코프 바깥을 나가기 전에 포인터를 모두 제거해야 되는 것이다. 위와 같은 상황에서 함수 안에서 자료를 할당한 후에 참조값만 리턴하게 되면 s는 스코프 바깥은 나가면서 데이터가 drop되지만 &s는 포인터 부분만 남아서 리턴되게 된다. 이러한 경우 에러가 나게 된다.

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                ~~~~~~~~

For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` due to previous error

이러한 에러이다. help 부분의 문구를 보면 더 자세히 설명된다.

 

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

fn dangle() -> &String { // dangle returns a reference to a String

    let s = String::from("hello"); // s is a new String

    &s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
  // Danger!

이 부분을 만약 에러 없이 고치려면

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

이런식으로 포인터만 있는 참조 값이 아닌 실제 소유권 까지 move 시켜주면 된다.

 

정리

reference의 규칙

1. 단 하나의 mutable reference만 가지거나 하나 또는 여러개의 imutable값들을  reference로 받아와야 한다.

2. reference는 항상 유효해야 한다.

728x90
반응형
Comments