러스트는 소유권이라는 개념을 통해 C++처럼 직접 메모리 할당/해제를 하거나 JS처럼 가비지 컬렉터를 사용하지 않아도 메모리를 관리한다.
- 꼭대기(Top)에 있는 값을 가져와서 쓰면 되기에 데이터 공간을 검색하지 않아도 된다.
- 스택에 담긴 모든 데이터는 크기가 고정되어있다.
그래서 스택에 저장된 데이터에 빠르게 접근할 수 있다.
가변크기의 데이터를 담기 위해 힙을 사용한다.
힙 메모리 할당 시 운영체제가 자유체인 속 빈 공간을 찾아다녀야 하기 때문에 데이터 접근이 느리다.
힙 데이터를 관리하기 위해 소유권이 존재한다.
- 모든 값은 오너(Owner)라고 불리는 변수가 있다.
- 한 번에 딱 하나의 오너만 존재할 수 있다.
- 오너가 스코프 밖으로 벗어나면, 값이 버려진다.
{ // s 없음
let s = "hello"; // s 생김
// s 가지고 뭔가 함
}
// 스코프 끝나서 s가 없어짐
하드코딩된 문자열, 변경 불가.
아마 스택에 저장될 듯?
스트링 리터럴은 힙도 스택도 아닌 프로그램 바이너리에 저장된다.
힙에 할당되고 변경 가능
let s = String::from("hello");
한번의 allocate
와 한번의 free
가 쌍을 이뤄야 한다.
스코프에 벗어난 변수는 drop
이라는 함수를 호출하여 메모리를 반환한다.
그렇다면 아래 코드의 경우
let s1 = String::from("hello");
let s2 = s1;
s1
과 s2
가 스코프 밖으로 벗어나면 하나의 할당 메모리에 두번 해체가 호출되어 메모리 손상이 일어날 수 있다.
러스트에서는 할당되는 메모리를 복사하는 대신에 s1
의 소유권을 s2
로 이동시킨다.
스코프를 벗어날 떄 s1
을 해제할 필요가 없게 만든다.
clone
메서드로 힙 데이터를 깊은 복사할 수 있다.
아래 코드의 s1
과 s2
둘 다 유효하다.
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
정수형과 같이 컴파일 시 크기가 결정되는 타입은 모두 스택에 저장된다.
복사본을 빠르게 만들 수 있기에 깊은 복사와 얕은 복사 간의 차이가 없다.
Copy
트레잇을 가진 타입들은 아래와 같이 값이 이동하는 대신 복사된다.
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
대입과 마찬가지로 함수에 변수를 넘길 때 값의 소유권도 넘어가거나 값이 복사된다.
함수를 사용하면서 소유권을 줬다가 다시 돌려받는 것은 매우 귀찮다.
함수에가 값을 사용할 수 있도록 하면서 소유권을 갖지 않도록 하기 위해 참조자라는 기능을 사용한다.