Rust 기본 개념 - 참조 및 빌림
이전 글에서 Rust의 소유권에 대해 알아보았습니다.
이번 글에선 참조 및 빌림에 대해 알아보겠습니다.
참조 및 빌림
이전에 튜플을 이용하는 방식은 소유권을 함수 밖으로 다시 가져온 형태인데,
해당 방법을 사용하는 대신 참조자를 이용해 해당 참조자를 인자로 사용하는 함수를 정의하고 사용할 수 있습니다.
러스트에는 여러종류의 참조자가 존재합니다. 우선 일반적인 불변 참조자 부터 알아보겠습니다.
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()
}
위 함수에선 & 라는 기호를 사용합니다. 이는 참조자를 의미하는 기호이며 해당 코드에선 s1변수의 참조자를 함수의 인자로 전달하는 용도로 사용합니다. 즉 소유권이 없는 값에대한 참조 값만 가진 &s1 인자를 함수에서 사용하도록 하는 형태입니다.
사용할때와 마찬가지로 정의할 때도 인자의 타입에 참조자임을 알려주고 있습니다. ( &String ) 이를 빌림 이라고 합니다.
calcaulate_length 함수가 끝나 스코프 밖으로 인자가 벗어나도 실제 s1의 소유권은 변하지 않으며, 메모리도 해제되지 않습니다.
참조값은 소유권이 없기 때문에 애초에 메모리에 대한 제어 권한 자체가 없습니다. 삭제는 물론이고 수정 자체도 불가능 합니다.
그래서 스코프가 끝나 drop함수가 실행되어도 아무런 일이 발생하지 않습니다.
따라서 참조자를 사용하면 소유권을 다시 이동해주지 않아도 됩니다.
그런데 만약 참조자를 이용해 받은 값은 수정하고 싶다면?
그저 참조자 이기 때문에 안됩니다.

사실 가변참조자를 사용하면 가능합니다.
가변 참조자
참조자는 기본적으로 불변인데 이 가변 참조자는 값을 수정할 수 있습니다.
우선 변수에 mut 키워드를 붙혀서 가변성을 가진다고 명시합니다.
그리고 참조자에도 mut를 붙혀주고 사용할 함수의 인자에도 mut를 붙힙니다.
fn main() {
let mut s = String::from("hello");
// mut 붙혀서 가변성을 가진다고 명시
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
이렇게되면 hello world를 반환할 수 있습니다
주의사항
참조자는 생성 규칙이 있습니다. 메모리 안정성을 위한 이유로 다음과 같은 규칙을 가집니다.
- 해당 변수의 스코프내에 가변 참조자는 딱 하나만 만들 수 있다.
- 가변 참조자와 불변 참조자를 동시에 생성할 수 없다.
이유는 다음과 같습니다.
- 데이터 레이스 방지: 가변 참조자 규칙을 통해 동일한 메모리 위치에 동시에 여러 개의 쓰기 작업이 발생하는 것을 방지합니다.
- 메모리 안전성 보장: 불변 참조자와 가변 참조자를 동시에 허용하지 않음으로써, 데이터의 일관성과 무결성을 유지합니다.
메모리 안정성을 위해 많은 것을 하고 있다는 점을 알 수 있습니다.
댕글링 참조자
포인터가 있는 언어에서는 메모리를 가리키는 포인터가 이미 존재하는 와중에, 메모리를 해제함으로써 해당 메모리 공간을 다른 개체에게 사용하도록 하여 댕글링 포인터가 생성될 가능성이 있습니다. 이미 해제된 순간에 유효하지 않은 메모리 공간을 가리키기에 벌써 에러 가능성을 내포하고 있어 주의해야할 문제 입니다.
반면에 러스트에서는 모든 참조자가 댕글링 참조자를 생성하지 않도록 보장합니다.
컴파일러에서 미연에 이를 방지해줍니다.
다음은 댕글링 참조자를 의도적으로 생성하는 코드입니다.
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s // 참조자 생성 및 반환
} // 컴파일에러: 반환값의 원래 값을 찾을 수 없음 ( 댕글링 포인터 )
s는 dangle이라는 함수 내부에서 생성되었기에 함수가 종료되면 자동으로 메모리가 해제됩니다.
그런데 메모리 해제된 참조자를 반환하고 있는 모습입니다. 위에서 말한 댕글링 참조자 입니다.
러스트는 이런 문제를 컴파일러가 차단해버린다고 합니다.

이외에도 슬라이스 참조자 라는게 있는데 다음 글에서 알아보겠습니다.