티스토리 뷰

함수가 값을 사용할 수 있도록 하되 소유권은 갖지 않도록 하고 싶으면 참조자(Reference)를 사용하면 된다.

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 함수를 정의한 것이다. 

calculate_length 함수에 &s1을 넘기고, 함수 정의 부분에는 &String을 사용했다. 여기서 사용된 & 기호가 참조자이며, 이는 어떤 값을 소유권을 넘기지 않고 참조할 수 있도록 한다. 소유권을 갖지 않기 때문에 참조자가 가리키는 값은 참조자가 스코프 밖으로 벗어나도 메모리 반납이 일어나지 않는다. 

 

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

 

가변 참조자(Mutable References)

하지만 참조자를 변경할 수 있게 할 수 있다. 

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

    change(&s);
}

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

그러기 위해서는 먼저 s를 mut로 바꿔야 한다. 그리고 &mut s로 가변 참조자를 생성하고 some_string: &mut String으로 이 가변 참조자를 받아야 한다. 

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

 

이 제한 사항은 가변을 허용하긴 하지만 매우 통제된 방식으로 허용한다. 이러한 제한이 가지는 이점은 러스트가 컴파일 타임에 data race를 방지할 수 있도록 해준다는 것이다. 

 

data race는 아래에 정리된 세 가지 동작이 발생했을 때 나타나는 특정 레이스 조건이다:

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

2. 그 중 적어도 하나의 포인터가 데이터를 쓴다.

3. 데이터에 접근하는데 동기화를 하는 어떠한 메커니즘도 없다.

 

data race는 정의되지 않은 동작을 일으키고 런타임에 이를 추적하고자 할 때 진단하고 고치기 어려울 수 있다. 러스트는 data race가 발생할 수 있는 코드를 컴파일도 못하도록 하기 때문에 문제의 발생을 막을 수 있다.

 

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

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

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

 

댕글링 참조자(Dangling References)

포인터가 있는 언어에서는 자칫 잘못하면 dangling pointer를 만들기 쉬운데, 이는 어떤 메모리를 가리키는 포인터를 보존하는 동안, 그 메모리를 해제함으로써 다른 개체에게 사용하도록 줘버렸을 지도 모를 메모리를 참조하고 있는 포인터를 말한다. 이와는 반대로, 러스트에는 컴파일러가 모든 참조자들이 댕글링 참조자가 되지 않도록 보장해준다. 만일 우리가 어떤 데이터의 참조자를 만들었다면, 컴파일러는 그 참조자가 스코프 밖으로 벗어나기 전에는 데이터가 스코프 밖으로 벗어나지 않을 것임을 확인해 줄 것이다.

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

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

    &s
}

이 코드는 컴파일 과정에서 오류가 발생한다. 함수의 반환 타입은 빌린 값을 포함하고 있는데, 빌려온 실제 값은 없기 때문이다. 

s가 dangle 안에서 만들어졌기 때문에, dangle의 코드가 끝이 나면 s는 할당 해제된다. 이것의 참조자를 반환하려고 했지만, 결국 참조자가 어떤 무효화된 String을 가리키게 될 것이기 때문에 잘못된 것이다.

따라서 함수를 아래와 같이 String을 직접 반환하도록 수정하면 된다. 소유권이 밖으로 이동되었고 아무것도 할당 해제되지 않기 때문에 문제가 없다.

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

    s
}

참조자의 규칙

1. 어떠한 경우이든 간에, 아래 조건 중 하나만 가질 수 있다.

    * 하나의 가변 참조자

    * 임의 개수의 불변 참조자들

2. 참조자는 항상 유효해야만 한다.

'개발 > Rust' 카테고리의 다른 글

1. 소유권  (0) 2023.05.01
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함