시뻘건 개발 도전기

러스트 9 - Ownership : 소유권 #1 본문

프로그래밍/RUST

러스트 9 - Ownership : 소유권 #1

시뻘건볼때기 2019. 2. 24. 14:08
반응형



Rust는 Ownership이라는 unique한 특징을 가진다.



개발을 하다보면 Memory Leak(메모리 누수)에 대해 깊은 생각에 빠질 때가 있다.

메모리를 안전하게 사용하라고 나온 녀석이 garbage collector인데, 이 녀석은 완벽하게 메모리를 safe하게 사용하도록 해주진 않는다.

rust는 이 garbage collector 마저 존재하지 않는다. 그렇다면 메모리 관리를 어떻게 해야할까?



언어 특징상 개발자가 직접적으로 메모리를 할당하고 해제하는 언어(대표적으로 C)가 있는가 하는 반면에 더 이상 사용되지 않는 메모리를 반환해주는 garbage collector를 가진 언어(대표적으로 java)가 있다.


rust의 메모리 관리는 다음과 같이 설명되어있다.

Memory is managed through a system of ownership with a set of rules that the compiler checks at compile time.

None of the ownership features slow down your program while it’s running.


메모리는 컴파일러가 compile을 진행할 때, Ownership이라고 하는 소유권 시스템을 통해 관리 된다. 이 소유권이라는 녀석은 "컴파일러가 체크해야할 규칙"으로 해석된다. (사실 나도 정확하게 뭐다! 라고 이해하지 못했다...ㅠㅠ)

확실한 것은 rust는 소유권이라고 하는 녀석으로 메모리 관리를 하는데 소유권을 잘 알고 있어야 프로그램으로부터 메모리가 안전하다는 것!!!






1. Ownership Rule


소유권의 규칙은 다음과 같다.

  • rust에서의 각 값은 "owner"라 불리는 변수를 갖는다.
  • 한 번에 하나의 owner만 가질 수 있다.
  • owner가 out of scope라면 값이 사라진다.


2. 변수 scope


fn main() {


    let str = "String";


    {

        let str2 = "String2";

        println!("isBlock str : {}", str);

        println!("isBlock str2 : {}", str2);

    }

    println!("str : {}", str);

    // println!("str2 : {}", str2);

}



str 변수는 문자열 리터럴을 나타탠다.

이 코드를 돌리면 과연 어떤 결과가 나올까?



그렇다면 가장 아래 주석을 해제하고 돌려보자.



컴파일 단계에서 전혀 str2를 알지 못한다.

즉, scope는 다음과 같이 이해하면 되겠다.


let 구문으로 변수가 선언되고, drop될 때까지 유효하다는 이야기. 즉, drop된 이후에 사용하려고하면 컴파일러는 찾지 못한다.




3. String Type


우리가 이전에 다룬 데이터 타입 (integer 등)의 경우, Stack에 쌓여 있다가 scope를 벗어나면(drop) Stack으로부터 pop이 되었다. 하지만 String의 경우 Heap에 저장되는 녀석이다. 즉, 사용되는 메모리 부터가 다르다는 이야기.

언제나 그렇듯.. 우리는 메모리 적재가 어떻게 이루어지는지 알아야 한다.


3-1. Stack


LIFO(Last in, First out) 방식을 사용하는 메모리. Stack에서의 메모리 적재를 "push"라 부르고 제거를 "pop"이라 부른다.

Stack은 빠르다.

왜? 새로운 데이터를 저장할 곳이나 데이터를 가져올 곳을 찾을 필요가 없기 때문. 그 이유는 항상 그 위치가 최상위에 있어서!

또 왜? Stack의 모든 데이터가 할당된 고정 크기를 차지해야한다는 것 때문.

컴파일될 때, Stack의 크기가 정해지지 않거나 변경되는 데이터를 위해, Heap에 데이터를 저장한다.


3-2. Heap


데이터가 Heap 영역에 저장될 때, 먼저 "저장할 공간 충분 해?"라고 OS에게 물어본다. OS는 저장할 공간의 포인터(메모리 주소)를 준다.

Heap은 Stack보다 느리다.

왜? OS가 준 포인터를 따라가야 하기 때문. 메모리 주소를 아니까 주소 따라가서 거기에 저장해야지..



rust가 제공해주는 문자열 리터럴을 사용해보자.

fn main() {

    let str = String::from("String");


    println!("str : {}", str);

}



콜론 두 개(::)는 "namespace 연산자"라고 부른다. "String 타입이 가지고 있는 from이라는 함수를 사용할꺼야"라는 말이 된다.

str 가지고 장난을 쳐보자.


fn main() {

    let mut str = String::from("String");

    // 이어 붙이기

    str.push_str(" Literal");

    println!("str : {}", str);

}


push_str 함수는 문자열 리터럴을 String 뒤에 붙이겠다는 녀석이다. (마치 concat과 유사함)


그렇다면 String과 문자열 리터럴의 차이는 무엇일까요?

정답은 메모리 사용 방식이 다르다고 할 수 있다.

반응형

'프로그래밍 > RUST' 카테고리의 다른 글

러스트 11 - References와 Borrowing  (0) 2019.03.02
러스트 10 - Ownership : 소유권 #2  (0) 2019.03.02
러스트 8 - 주석  (0) 2019.02.23
러스트 7 - 제어문과 반복문  (0) 2019.02.23
러스트 6 - 함수  (0) 2019.02.17
Comments