소유권과 수명

소유권

소유권은 가비지 컬렉터 없이 런타임 메모리 안전과 성능을 보장하기 위한 컴파일 타임 정보이다.

소유권을 통해 메모리 안전과 성능 뿐 아니라 스레드 안전까지 보장된다.

소유권의 3 원칙

  1. 각 값에는 해당 값의 오너 변수가 있다.
  2. 한 값에 한 순간에 한 오너만이 존재한다.
  3. 오너가 스코프를 벗어나면 값은 버려진다. 

러스트는 기본적으로 이동 의미론을 사용한다. 이동 의미론이란 코드 내에서 대입이나 인수 전달 등 값의 전달이 일어나는곳에서 값이 이동된다고 해석하는 방식을 말한다. 반대로 복사 의미론은 전달이 일어나는 곳에서 값의 복사가 일어나도록 해석한다. 

이동이 일어나는 순간들

  1. 대입
  2. 함수 인자로 전달
  3. 구조체/열거형
    • 패턴매칭(언패킹)
    • 필드 접근(부분 이동): 구조체 필드를 다른 변수에 대입하는경우 해당 필드만 무효가 된다.
  4. 함수의 리턴값
  5. 메서드에서 &self가 아닌 self 사용: 해당 메서드를 호출한 객체가 메서드 내에서 소비된다.
  6. move 클로저: 클로저는 외부환경을 기본적으로 빌려오지만 move |..|{...} 는 소유권을 가져온다.

소유권 이동 없이 값을 전달하는방법

  1. 참조로 전달(&, &mut)

  2. 복제(Copy, Clone)

  3. 섀도잉으로 다시 받기(let a = f(a);)

할당되지 않은 값(임시값)의 소유권

변수에 담기지않은 임시값은 그 줄이 끝나는 즉시 소멸한다. 하지만 그 임시값에 참조가 있으면 임시값 수명연장이 발생한다.

임시값 수명연장은 컴파일러가 익명변수를 만들어 다른 변수들처럼 임시값 참조가 수명이 다할 때까지 유효하도록 해준다.

다만 수명연장은 참조를 let 변수에 대입할 때만 나타난다.

다른 복잡한 경우(메서드 호출 체이닝 등)에는 컴파일러가 도와주지 않는다. 이러한 경우 명시적으로 임시값을 변수에 할당하는식으로 해결해야 한다. 

수명

수명의 사용 예제

  1. 함수의 인자와 반환값
    // "결과값(&str)은 입력값(s1, s2) 중 더 짧게 사는 놈('a)만큼만 살 수 있다."
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() { x } else { y }
    }
  2. 구조체의 필드
    // "ImportantExcerpt라는 구조체는 'a라는 참조를 품고 있다."
    // "따라서 'a가 가리키는 원본이 사라지면, 이 구조체도 더 이상 못 쓴다."
    struct ImportantExcerpt<'a> {
        part: &'a str,
    }
    
    fn main() {
        let novel = String::from("스타워즈...");
        let first_sentence = novel.split('.').next().expect("");
        
        // 구조체 생성 (참조를 필드에 저장)
        let i = ImportantExcerpt {
            part: first_sentence,
        };
    }
  3. 제네릭 타입 바운드
    // "T 타입 안에 참조가 있다면, 그 참조들은 전부 'a 보다는 오래 살아야 한다."
    struct Ref<T, 'a> where T: 'a {
        r: &'a T,
    }

 

사실 1, 2의 경우도 제네릭을 이용하므로 3을 특정 상황에 적용한 것으로 볼수 있다. 

수명을 표시한다는것은 수명 기간을 지정하는 것이 아닌 참조 간에 수명 관계를 명시하는것으로 받아들여야 한다.

접근제어

소유자: 읽기, 쓰기, 이동, 드롭

가변 참조: 읽기, 쓰기

불변 참조: 읽기

대여규칙

불변 참조는 여럿일 수 있다.

가변참조는 하나만 존재해야 한다.

불변 레퍼런스가 존재하면 소유자는 값을 읽을 수만 있다

가변 레퍼런스가 존재하면 소유자는 값을 접근할 수 없다. 

-> 어떤종류든 레퍼런스가 있으면 소유자는 값을 이동, 드롭할 수 없다.

러스트는 대여규칙을 스레드 간에도 적용하여 데이터 경쟁 문제를 근본적으로 차단한다.