[Rust]

[Rust] 소유권

극꼼 2023. 7. 25. 00:58
반응형


<소유권>

: 러스트의 가장 유니크한 특성으로, 러스트가 가비지 콜렉터 없이 메모리 안정성을 보장할 수 있도록 해줍니다.

어떤 언어는 프로그램이 실행될 때 사용하지 않는 메모리를 찾는 가비지 컬렉션을 사용하고, 어떤 언어는 프로그래머가 직접 명시적으로 메모리를 할당하고 해제해줍니다. 러스트는 이런 방법을 사용하지 않고, 컴파일 타임에 컴파일러가 체크할 규칙으로 구성된 소유권 시스템을 통해 메모리를 관리합니다.

소유권에 대해 알아보기 앞서 스택과 힙이 무엇인지 짚고 넘어가겠습니다.

 

* 스택과 힙

: 러스트에서는 값이 스택에 있는지, 힙에 있는지에 따라 프로그래머의 결정에 큰 영향을 줍니다. 스택과 힙 둘 다 런타임에 사용할 수 있는 메모리 영역입니다.

 

스택은 Last in, First out으로, 데이터를 추가하는 것을 push, 데이터를 제거하는 것을 stack이라 합니다. 스택 다음과 같은 두가지 이유로 빠르게 사용할 수 있습니다.

1) 새로운 데이터를 넣는 공간이 항상 스택의 꼭대기(top)이기 때문에 메모리 공간을 검색할 필요가 없어서 빠릅니다.

2) 스택에 담긴 모든 데이터는 고정된 크기를 가지고 있습니다.

 

컴파일 타임에 크기가 정해져 있지 않거나 변할 수 있는 데이터는 힙에 저장할 수 있습니다. 데이터를 힙에 넣기 위해서는 저장할 공간이 있는지 검색해야 합니다. 운영체제는 힙 안의 특정 지점에 사용중이라는 표시를 하고 해당 지점의 포인터를 돌려주며, 이 과정을 allocating이라 합니다. 포인터가 가리키는 곳으로 이동해야 하기 때문에 스택보다 데이터에 접근하는 속도가 느립니다. 힙으로부터 큰 공간을 할당받는 것도 시간이 걸릴 수 있습니다.

앞으로 배울 소유권은 코드의 어느 부분이 힙의 어떤 데이터를 사용하는지 추적하는 것, 힙의 중복된 데이터 양을 최소화하는 것, 힙 내에 사용하지 않는 데이터를 제거해서 공간이 모자라지 않게 하는 것을 다룰 예정입니다.


<소유권 규칙>

1. 러스트의 각각의 값은 해당 값의 owner 변수를 가지고 있습니다.

2. 한 번에 하나의 오너만 존재할 수 있습니다.

3. 오너가 스코프 밖으로 벗어나면 값은 버려집니다.

 

String은 힙 메모리에 할당되는 타입으로, 고정된 값의 문자열이 아니라 크기를 증가시키거나 축소할 수 있는 문자열입니다.

스트링 리터럴로부터 from 함수를 이용해 String 타입을 만들어 예제로 살펴보겠습니다.

    * 스트링 리터럴(string literal) : 큰 따옴표로 둘러싼 문자의 연속체

    * 더블 콜론(::)은 String 타입의 from 함수를 특정지을 수 있게 해주는 네임스페이스 연산자

{
    //owner 변수 s.
    let mut s = String::from("hello"); //String::from을 호출할 때 런타임에 운영체제로부터 메모리 요청
    s.push_str(", wrold!"); //push를 통해 스트링 리터럴을 String에 붙여줌.
    println!("{}",s); //hello, world! 출력.
} //스코프 끝 = s는 더 이상 유효하지 않음.

 

String이 스코프 밖을 벗어날 때 러스트는 drop이라는 함수를 호출하는데, String의 개발자가 메모리를 반환하도록 하는 코드를 drop에 넣어두었기 때문에 String의 메모리를 운영체제에게 반납하게 됩니다.


이제 힙에 할당시킨 데이터를 사용하는 여러 변수가 상호작용하는 예시를 살펴보겠습니다.

 

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

let x = 5;
let y = x;

다음은 변수 x 의 값을 y에 대입한 것입니다. 정수값 5를 x에 묶어놓고, x 값의 복사본을 만들어서 y에 묶어둡니다.

 

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

String은 동작하는 방식이 조금 다릅니다. String은 문자열의 내용을 다음과 같이 스택에 포인터, 길이, 용량 부분으로 나눠서 가지고 있습니다. 포인터는 문자열 각각의 힙 메모리에 대한 포인터를 가지고 있습니다.

s2에 s1을 대입할 경우 String의 데이터가 복사되는데, 이는 스택에 있는 포인터, 길이, 용량 값이 복사되는 것을 의미하며, 힙 메모리 내의 데이터가 복사되는 것이 아니라 포인터 값을 그대로 복사해옵니다. 따라서 s2는 s1과 같은 힙 메모리를 공유하게 됩니다.

s1, s2가 같은 힙메모리를 공유하고 있는 상황에서 변수가 스코프를 벗어날 때 둘 다 같은 메모리를 해제하게 되어 double free 라고 하는 메모리 안정성 버그가 발생합니다. 메모리를 두 번 해제하는 것은 메모리 손상의 원인이 되며 보안 취약성 문제를 일으킬 수 있습니다.

러스트는 이런 메모리 안정성을 보장하기 위해, s1이 더 이상 유효하지 않다고 간주합니다. 따라서 다음과 같이 코드를 작성할 경우 error[E0382]: borrow of moved value: `s1` 에러가 출력되는 것을 확인할 수 있습니다.

let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!",s1); // borrow of moved value: `s1` 에러 발생

이와 같은 상호작용을 s1이 s2로 이동되었다 라고 하며, 스코프를 벗어날 때 유효한 s2의 메모리만 해제해주면 됩니다.

 

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

만약 String의 힙 데이터를 깊이 복사해오기를 원한다면 clone 메서드를 사용할 수 있습니다.

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

 

3. 스택에만 있는 데이터 : 복사

let x = 5;
let y = x;

앞서 살펴본 예시를 다시 보겠습니다. 스택에 저장될 수 있는 정수형과 같은 타입은 복사본이 빠르게 만들어지며, y가 생성된 후에 x가 유효하지 않도록 할 이유가 없습니다. 러스트는 정수형과 같이 스택에 저장할 수 있는 타입에 대해 Copy trait이라 불리는 특별한 annotation을 가지고 있습니다(trait에 대해서는 이후에 다시 살펴보겠습니다). 어떤 타입이 Copy trait을 가지고 있다면 대입 후에도 예전 변수를 유효하게 해줍니다. 러스트는 어떤 타입이 Drop trait을 구현했다면 Copy trait을 어노테이션 할 수 없게 합니다. 

Copy가 가능한 타입들은 다음과 같습니다.

1) u32와 같은 모든 정수형 타입

2) bool

3) f64와 같은 모든 부동 소수점 타입

4) Copy가 가능한 타입으로만 구성된 튜플


<소유권과 함수>

함수에게 값을 넘기는 것은 값을 변수에 대입하는 것과 유사하게 이동하거나 복사될 것입니다. 

fn main() {
    let s1 = String::from("hello");
    let s3 = s1.clone();
    takes_ownership(s3); //s3 값이 함수로 이동하여 유효하지 않음
}

fn takes_ownership(some_string: String){
    println!("{}",some_string);
} // drop이 호출되어 some_string 메모리 해제

<반환 값과 스코프>

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string //some_string이 반환되어 호출한 함수로 이동
}

위와 같은 값의 반환 또한 소유권을 이동시킵니다. 원래는 스코프를 벗어나는 값이 반환되어 다른 변수에 의해 소유되었으므로 drop에 의해 제거되지 않습니다.


요약에 사용된 참조 링크 : https://rinthel.github.io/rust-lang-book-ko/ch04-00-understanding-ownership.html

반응형

'[Rust]' 카테고리의 다른 글

[Rust] 슬라이스(slices)  (0) 2023.07.27
[Rust] 참조자(References)와 빌림(Borrowing)  (0) 2023.07.26
[Rust] 제어문  (0) 2023.07.20
[Rust] 함수 동작 원리  (0) 2023.07.19
[Rust] 데이터 타입  (0) 2023.07.18