[Rust] References와 Borrowing

silver·2023년 4월 14일
0
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의 값을 참조하지만 소유하지는 않는 참조자를 생성하도록 해준다.
소유권을 갖고 있지는 않기 때문에, 이 참조자가 가리키는 값은 참조자가 스코프 밖으로 벗어났을 때도 메모리가 반납되지 않는다.

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

만일 참조한 값을 고치려고 시도한다면 무슨 일이 발생할까?

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

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

오류가 발생한다.

error: cannot borrow immutable borrowed content `*some_string` as mutable
 --> error.rs:8:5
  |
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^

변수가 기본적으로 불변인 것처럼, 참조자도 마찬가지이다.

Mutable References

위의 코드를 약간만 변형하면 오류가 발생하지 않는다.

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

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

먼저 smut로 변경해야 한다. 그리고 &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

이 제한 사항은 가변을 허용하긴 하지만 매우 통제되 형식으로 허용한다. 이러한 제한으로 인해 데이터 레이스(data race)를 방지 할 수 있다.

Data Race
데이터 레이스는 아래에 정리되 세 가지 동작이 발생했을 때 나타나는 특정한 레이스 조건이다.
1. 두 개 이상의 포인터가 동시에 같은 데이터를 접근한다.
2. 그 중 적어도 하나의 포인터가 데이터를 쓴다.
3. 데이터에 접근하는데 동기화를 하는 어떠한 매커니즘도 없다.

항상 우리는 새로운 스코프를 만들기 위해 중괄호를 사용하는데, 이는 그저 동시에 만드는 것이 아니게 해줌으로써, 여러 개의 가변 참조자를 만들 수 있도록 해준다.

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

댕글링 포인터(dangling pointer)
어떤 메모리를 가리키는 포인터를 보존하는 동안, 그 메모리를 해제함으로써 다른 개체에게 사용하도록 줘버렸을지도 모를 메모리를 참조하고 있는 포인터를 말한다.

러스트에서는 컴파일러가 모든 참조자들이 댕글링 참조자가 되지 않도록 보장한다: 만일 우리가 어떤 데이터의 참조자를 만들었다면, 컴파일러는 그 참조자가 스코프 밖으로 벗어나기 전에는 데이터가 스코프 밖으로 벗어나지 않을 것임을 확인해 준다.

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

fn dangle() -> &String {
	let s = String::from("hello");
    
    &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

해당 에러 내용의 첫 번째 help 부분을 읽어 보면,
이 함수의 반환 타입은 빌린 값을 포함하고 있는데, 빌려온 실제 값은 없습니다.
라고 말하고 있다.

코드를 좀 더 상세히 살펴보면,

fn dangle() -> &String {	// dangle 은 String 의 참조자를 반환한다.
	
    let s = String::from("hello");	// s 는 새로운 String 이다.

	&s	// String s 의 참조자를 반환한다.
}	// 여기서 s 는 스코프를 벗어나고 버려진다. s 의 메모리는 사라진다.
	// 위험하다!!!

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

이것을 해결하기 위해서는 String을 직접 반환하는 것이다.

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

이 코드에서는 소유권이 밖으로 이동 되었고, 아무것도 할당 해제되지 않는다.

참조자의 규칙

  1. 어떠한 경우이든 간에, 아래 둘 중 하나만 가질 수 있다:
    -. 하나의 가변 참조자
    -. 임의 개수의 불변 참조자
  2. 참조자는 항상 유효해야만 한다.

참고: https://rinthel.github.io/rust-lang-book-ko/ch04-02-references-and-borrowing.html

0개의 댓글