RustBook - 4.1 Ownership

숲사람·2022년 3월 23일
0

Rust

목록 보기
7/15

이 시리즈는 Rust Book을 공부하고 정리한 문서입니다. 댓글로 많은 조언 부탁드립니다.
Rust Book: https://doc.rust-lang.org/book/


4.1 Ownership

전통적으로는 Runtime 에 할당되는 메모리(Heap)를 관리하기 위해서는 다음과 같은 방법이 있었다.

  • 프로그래머가 직접 할당 및 해제 (C, C++)
  • Garbage Collector 가 계속 수행되며 메모리 해제 (Java)

전자는 프로그래머가 실수로 메모리할당을 해제하지 않는 경우가 빈번히 발생하고(리눅스 커널 코드에도 상당히 많다) 후자는 런타임에 실행되며 컴퓨팅 자원을 사용하여 프로그램이 느려질 수 있는 문제가 있다. 반면 Rust 소유권 개념을 사용해 컴파일 타임에 안전하게 메모리를 관리 할 수 있다. 오너쉽은 러스트 언어의 가장 큰 장점이자 핵심 feature 이다.

소유권이란?
소유권은 메모리를 해제할 권리이다. 소유권을 가진 것만 메모리를 해제할수 있다.

결론:

  • 스택에 저장되는 값은 copy되며,
  • 힙에 저장되는 값은 move 된다. move는 소유권도 함께 이동하는것이다.

Stack and Heap

  • 스택에 저장되는 것들은 함수에 선언된 변수들, 넘겨받는 매개변수들이다. 이들은 함수가 call 될때 스택에 차곡차곡 쌓인다. 이를 Activation Recode 라고 한다. 스택에 할당되는 메모리는 컴파일타임에 그 크기가 결정되어야 한다. 그래서 메모리 접근이 빠르다.
  • 반면 힙은 프로그램의 실행 타임에 할당되고 해제된다. 더 느리다.

소유권은 스택이 아닌 힙메모리에만 적용된다.

힙에 데이터를 갖고 있는 변수가 스코프 밖으로 벗어나면, 해당 값은 데이터가 다른 변수에 의해 소유되도록 이동하지 않는한 drop에 의해 제거된다.

오너쉽 규칙

    1. 러스트의 각각의 값은 해당값의 오너(owner)라고 불리우는 변수를 갖고 있다.
    1. 한번에 딱 하나의 오너만 존재할 수 있다.
    1. 오너가 스코프 밖으로 벗어나는 때, 값은 버려진다(dropped).

변수의 스코프

스코프는 프로그램 내에서 아이템이 유효한 범위이다. 다음의 변수 s는 하드코딩된 스트링 리터럴 값이 있다. 이 변수는 } 를 빠져나가면 더이상 유효하지 않다.

{
    let s = "hello";
}

참고로 스트링 리터럴은 immutable 이다. 값을 변경 할 수 없다.

String 타입

반면 String 타입은 런타임에 값의 변경이 가능 하다.

let  mut s = String::from("hello"); 
s.push_str(", world!"); // push_str() appends a literal to a String    
println!("{}", s); // `hello, world!` will be printed.

스트링 타입과 스트링 리터럴 타입은 어떻게 메모리를 할당할까?

Memory and Allocation

러스트는 변수가 소속되어있는 스코프 밖을 벗어나는 순간 메모리를 자동으로 해제한다. 위의 예제에서 s 가 스코프 밖을 벗어날 때 drop 이라는 함수가 호출된다. 러스트는 } 괄호가 닫힐때 자동으로 drop을 호출한다.

변수와 데이터가 상호작용하는 방법: 이동(move)

let x = 5; 
let y = x;

이 경우는 x, y모두 스택에 push 되기 때문에 둘다 5 값을 갖는다.

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

반면 String 버전은 다르다. 여기서 스트링값은 s1 에서 s2로 복사가 아니라 이동(move) 되었다고 표현한다. s1은 무효화 된다. 메모리 구조는 아래와 같다. 왼쪽의 s1, s2는 포인터와 길이 등을 갖는 고정된 크기이며 스택에 저장 된다. 오른쪽은 힙에 저장될것이다.

s1 moved to s2

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

{
    let s2 = s1;
}
println!("{}", s1);

이 코드는 컴파일 에러가 발생한다. s1은 무효화 되었기 때문이다.

변수와 데이터가 상호작용하는 방법: 클론

만약 힙 데이터까지 복사하고 싶다면 clone() 메소드를 사용할 수 있다. 하지만 힙 공간을 두배로 사용.

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

{
    let s2 = s1.clone();
    println!("s1 = {}, s2 = {}", s1, s2);
}
println!("{}", s1);

s2 는 소유권을 넘겨받지 않고 복사를 했기때문에 s1 은 무효화되지 않는다. 이 코드는 동작한다.

어떤 타입을 copy 할 수 있나?

다음과 같이 스택에 저장되는 변수는 copy 된다. 이것은 소유권 개념이 적용되지 않는다.

let x = 5; 
let y = x;

다음과 같은 타입들이 copy 될 수 있다.

  • u32와 같은 모든 정수형 타입들
  • truefalse값을 갖는 부울린 타입 bool
  • f64와 같은 모든 부동 소수점 타입들
  • Copy가 가능한 타입만으로 구성된 튜플들. (i32, i32)Copy가 되지만, (i32, String)은 안됨.

소유권과 함수

함수의 인자로 변수를 전달하면 소유권또한 같이 이동한다. 아래 주석을 자세히 보자.

fn main() {
    let s = String::from("hello");  // s comes into scope
    takes_ownership(s);             // s's value moves into the function...
                                    // 스트링 타입 변수 s 는 이지점에서 더이상 유효하지 않다.
    let x = 5;                      // x comes into scope
    makes_copy(x);                  // x would move into the function,
                                    // x 는 copy 된 것이므로 계속 x를 사용할 수 있다. 
} // s 에 대한 소유권이 이전되었기 때문에 s에 대한 drop 은 여기서 일어나지 않는다. x 는 스코프를 벗어났다.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

리턴값과 스코프

heap에 할당된 값을 리턴 하면 오너쉽도 리턴된다.

만일 함수에게 값만 넘기고 소유권은 넘기지 않도록 하고 싶다면? & 참조자(references) 를 사용할 수 있다. (5.2장)

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return
                                        // value into s1
    let s2 = String::from("hello");     // s2 comes into scope
    let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                        // takes_and_gives_back, which also
                                        // moves its return value into s3
} // s1, s3 은 drop 이 호출된다. s2는 drop 이 호출되지 않는다. 


fn gives_ownership() -> String {             
    let some_string = String::from("hello"); // some_string comes into scope
    some_string                              // some_string 이 move 된다. 
}

// takes_and_gives_back will take a String and return one
fn takes_and_gives_back(a_string: String) -> String { 
    a_string  // a_string is returned and moves out to the calling function
}
profile
기록 & 정리 아카이브용

0개의 댓글