레퍼런스와 대여



힙 데이터를 가리키는 변수의 오너십을 유지하면서 포인터를 넘기는 방법이 있음.


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 에 주목. calculate_length 를 호출하면서 &s1 을 넘겨, String 이 아닌 &String 을 넘겼음.


앰퍼샌드는 레퍼런스를 의미함. 앰퍼샌드는 오너십을 넘기지 않으면서 포인터를 전달할 수 있도록 함.


&String s 는 String s1 를 가리킴.


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


let len = calculate_length(&s1);


&s1 은 s1 의 값을 가리키지만, 오너십을 가지지 않음. 때문에 s1 은 여전히 유효한 변수로 남음.


fn calculate_length(s: &String) -> usize {        // s 는 &String 으로 넘겨받은 값을 가리킴.

s.len()

}                                                          // 여기서 s 는 유효 범위를 벗어나지만, s 는 오너십을 가지지 않았기 때문에 

  // 메모리 해제와 같은 일은 일어나지 않음.


이렇게 레퍼런스를 함수 파라미터로 빌리는 일을 '대여(borrowing)' 라 함.


이렇게 대여한 레퍼런스의 값을 바꾸려 한다면?


fn main() {

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


change(&s);

}


fn change(some_string: &String) {

some_string.push_str(", world");

}

위 코드는 컴파일 에러를 일으킴. 변수가 기본적으로 임뮤터블이듯, 레퍼런스도 마찬가지.



뮤터블 레퍼런스


아래 방법으로 레퍼런스의 값 변경 가능.


fn main() {

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


change(&mut s);

}


fn change(some_string: &mut String) {

some_string.push_str(", world");

}


먼저, 변수 s 가 뮤터블이어야 함. 그리고 레퍼런스도 &mut 로 얻고, 함수 파라미터도 &mut 으로 받아야 함. 이렇게 하면 레퍼런스의 값을 변경할 수 있음.


그런데 여기에는 한가지 큰 제약이 있음. 뮤터블 레퍼런스 대여는 동시에 하나를 초과할 수 없음.


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


let r1 = &mut s;

let r2 = &mut s; 

위 코드는 컴파일 에러 를 일으킴.


이 제약은 러스트만의 특성. 다른 언어는 보통 이를 허용함. 러스트는 이 제약을 통해 데이터 경합을 컴파일 타임에 방지함.


아래의 방식은 가능함.


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


{

let r1 = &mut s;

}                        // 여기서 r1 은 유효 범위를 벗어나 사라짐. 이제 새로운 레퍼런스를 생성해도 문제 없음.


let r2 = &mut s;


반면, 임뮤터블은 이 제약에서 자유로움. 왜냐하면 임뮤터블의 값은 태생적으로 변경될 수 없기에, 데이터 경합이 발생해도 안전하기 때문.


그러나, 뮤터블과 임뮤터블을 동시에 생성하면 역시 컴파일 에러 발생. 임뮤터블 변수를 사용할 때는 값이 변경되지 않을 것이라 여김. 그런데 뮤터블 레퍼런스가 생성되어 값을 변경하게 되면 곤란한 상황이 발생하기 때문에 이를 방지하기 위한 제약.



댕글링 레퍼런스


메모리가 해제되어 포인터가 가리키던 값이 사라졌는데, 포인터만 여전히 남아 그 주소를 가리키는 경우, 이 포인터를 댕글링 포인터라 함. 러스트는 역시 컴파일 타임에 이를 방지함.


fn main() {

let reference_to_nothing = dangle();        // 함수 dangle 이 정상적으로 수행되면 String 레퍼런스를 반환

}


fn dangle() -> &String {            // 이 함수는 String 레퍼런스를 반환

let s = String::from("hello");        // String 생성


&s                                        // String 레퍼런스를 반환.

}                                                // 변수 s 가 유효 범위를 벗어나 사라짐. 가리키던 String 도 메모리 해제됨.   

위 코드는 컴파일 에러를 일으킴.



레퍼런스 규칙 정리


1. 언제든 다음 중 하나의 레퍼런스만 취할 수 있음

  • 오직 하나의 뮤터블 레퍼런스
  • 숫자에 상관없는 임뮤터블 레퍼런스
2. 레퍼런스는 언제나, 반드시 유효해야 함.





















'Rust' 카테고리의 다른 글

구조체(기본)  (0) 2018.02.19
오너십(슬라이스)  (0) 2018.02.17
오너십  (0) 2018.02.15
컨트롤 플로우(루프)  (0) 2018.02.15
컨트롤 플로우(if 조건문)  (0) 2018.02.15

+ Recent posts