이 시리즈는 Rust Book을 공부하고 정리한 문서입니다. 댓글로 많은 조언 부탁드립니다.
Rust Book: https://doc.rust-lang.org/book/
전통적으로는 Runtime 에 할당되는 메모리(Heap)를 관리하기 위해서는 다음과 같은 방법이 있었다.
전자는 프로그래머가 실수로 메모리할당을 해제하지 않는 경우가 빈번히 발생하고(리눅스 커널 코드에도 상당히 많다) 후자는 런타임에 실행되며 컴퓨팅 자원을 사용하여 프로그램이 느려질 수 있는 문제가 있다. 반면 Rust 소유권 개념을 사용해 컴파일 타임에 안전하게 메모리를 관리 할 수 있다. 오너쉽은 러스트 언어의 가장 큰 장점이자 핵심 feature 이다.
소유권이란?
소유권은 메모리를 해제할 권리이다. 소유권을 가진 것만 메모리를 해제할수 있다.
결론:
copy
되며, move
된다. move
는 소유권도 함께 이동하는것이다. 소유권은 스택이 아닌 힙메모리에만 적용된다.
힙에 데이터를 갖고 있는 변수가 스코프 밖으로 벗어나면, 해당 값은 데이터가 다른 변수에 의해 소유되도록 이동하지 않는한 drop
에 의해 제거된다.
스코프는 프로그램 내에서 아이템이 유효한 범위이다. 다음의 변수 s는 하드코딩된 스트링 리터럴 값이 있다. 이 변수는 }
를 빠져나가면 더이상 유효하지 않다.
{
let s = "hello";
}
참고로 스트링 리터럴은 immutable 이다. 값을 변경 할 수 없다.
반면 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.
스트링 타입과 스트링 리터럴 타입은 어떻게 메모리를 할당할까?
러스트는 변수가 소속되어있는 스코프 밖을 벗어나는 순간 메모리를 자동으로 해제한다. 위의 예제에서 s
가 스코프 밖을 벗어날 때 drop
이라는 함수가 호출된다. 러스트는 }
괄호가 닫힐때 자동으로 drop
을 호출한다.
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는 포인터와 길이 등을 갖는 고정된 크기이며 스택에 저장 된다. 오른쪽은 힙에 저장될것이다.
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
와 같은 모든 정수형 타입들true
와 false
값을 갖는 부울린 타입 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
}