[Rust]

[Rust] 참조자(References)와 빌림(Borrowing)

극꼼 2023. 7. 26. 03:08
반응형


<참조자>

함수에게 값을 사용할 수 있도록 넘겨주되 소유권을 넘기고 싶지 않을 때, 다음과 같이 튜플로 여러 값을 돌려받을 수 있습니다.

fn main() {
    let s1 = String::from("hello");
    let (s2, len) = calculate_length(s1);
}

fn calculate_length(s:String)-> (String,usize){
    let length = s.len();
    (s, length)
}

하지만 이는 조금 과한 작업이고, 러스트는 이처럼 소유권을 넘기고 싶지 않을 때 참조자(references)라 부르는 기능을 사용할 수 있습니다.

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length2(&s1);
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length2 (s: &String) -> usize{
    s.len()
} //스코프 밖으로 s가 벗어나도 메모리 해제가 일어나지 않음

calculate_length2 함수에 &s1을 넘기고, 함수의 정의 부분에는 &String을 사용했습니다. 엠퍼센드(&) 기호는 참조자를 의미하며, 어떤 값의 소유권을 넘기지 않고 참조할 수 있도록 해줍니다. s와 s1의 메모리 구조는 다음과 같습니다.

이처럼 함수의 파라미터로 참조자를 만드는 것을 borrowing이라 합니다.

만약 빌린 값을 수정하려고 할 경우 다음 에러가 호출될 것입니다 : error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference

fn calculate_length2 (s: &String) -> usize{
    s.push_str(", world"); // 에러
    s.len()
}

변수가 불변인 것처럼 참조자도 불변입니다.


<가변 참조자(Mutable References)>

위에서 나온 에러가 나오지 않게 하기 위해서는 먼저 s 변수를 mut로 바꿉니다. s를 함수의 매개변수로 넘겨줄 때 &mut s 로 가변 참조자를 생성하고, 함수에서는 &mut String으로 가변 참조자를 받아줍니다.

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

fn calculate_length3 (s: &mut String) -> usize{
    s.push_str(", world");
    s.len()
}

 

가변 참조자는 특정 스코프(선언한 곳~ 마지막으로 사용한 곳) 내에서 특정 데이터에 대한 가변 참조자를 단 하나만 만들 수 있다는 제한이 걸려있습니다. 

let r1 = &mut s;
let r2 = &mut s;
//여기까지는 문제 없음.
println!("{}, {}", r1, r2); //이렇게 r1, r2를 같이 사용하려고 할 때 에러 출력

다음과 같이 여러 가변 참조자를 만들 경우 에러가 출력됩니다 : error[E0425]: cannot find value `r3` in this scope

가변 참조자의 이러한 제한은 컴파일 타임에 data race를 방지해줍니다.

다만, 선언한 곳~ 마지막으로 사용한 곳까지 겹치지 않으면 에러가 출력되지 않습니다.

let r1 = &mut s;
let r2 = &mut s;
println!("{}", r2); // 이건 가능
// 포인트는 r1의 사용 범위와 r2의 사용 범위가 겹치지 않는 것.

 

* 데이터 레이스(data race)

: 멀티스레드/프로세스 환경에서 일어난 오류를 의미하며, 다음 일련의 동작이 일어났을 때 나타납니다.

1) 2개 이상의 포인터가 동시에 같은 데이터에 접근

2) 그 중 적어도 하나의 포인터가 데이터를 사용

3) 데이터 동기화를 하는 메커니즘이 없음

 

 데이터 레이스는 런타임 때 추적하려 하면 고치기 어려울 수 있습니다. 따라서 러스트는 데이터 레이스가 발생할 수 있는 코드는 컴파일할 수 없게 막아버립니다.

 

let mut s = String::from("hjello");
{
    let r1 = &mut s;
}
let r2 = &mut s;

가변 참조자를 다음과 같이 스코프 안에 만들면 스코프가 끝날 때 r1은 스코프 밖을 벗어나서 유효하지 않기 때문에(동시에 같은 데이터에 접근할 일이 없기 때문에) 스코프 밖을 벗어났을 때 새로운 가변 참조자를 만들 수 있습니다.

 

불변 참조자의 경우에는 동시에 여러개를 만들 수 있습니다. 하지만 다음 예시와 같이 가변 참조자와 불변 참조자를 혼용할 경우 컴파일 에러가 발생할 수 있습니다 : error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable

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

let r1 = &s; // 문제 없음
let r2 = &s; // 문제 없음
let r3 = &mut s; // 큰 문제

<댕글링 참조자(Dangling References)>

* 댕글링 포인터 : 해제된 메모리 영역을 가리키고 있는 포인터입니다. 더이상 유효하지 않은 메모리이므로 댕글링 포인터를 사용하면 에러가 발생합니다 : error[E0106]: missing lifetime specifier

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

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

    &s
}

s변수는 dangle 함수의 스코프 영역 밖을 벗어날 때 메모리가 해제됩니다. 따라서 &s는 유효하지 않은 값으로 변합니다.


요약한 참조 링크 : https://rinthel.github.io/rust-lang-book-ko/ch04-02-references-and-borrowing.html

반응형

'[Rust]' 카테고리의 다른 글

[Rust] 데이터 구조체  (0) 2023.07.29
[Rust] 슬라이스(slices)  (0) 2023.07.27
[Rust] 소유권  (0) 2023.07.25
[Rust] 제어문  (0) 2023.07.20
[Rust] 함수 동작 원리  (0) 2023.07.19