Ownership, Move

이정후·2023년 7월 9일
0

Rust

목록 보기
2/13

들어가기에 앞서

스택과 힙

Rust에서는 값이 스택에 있는지, 힙에 있는지에 따라 언어의 동작 방식이 달라진다.
스택에 들어가는 데이터는 고정된 크기를 가지고 있어야 한다. 컴파일 타임에 크기가 결정되어 있지 않거나, 크기가 변경될 수 있는 데이터를 위해서는 힙에 데이터를 저장할 수 있다.

이 경우에는 우리의 시스템이 충분히 큰 공간을 마련하여, 해당 지점의 포인터를 개발자에게 알려준다.
우리는 이 과정을 allocating, 할당이라고 부른다. 스택에 포인터를 push 하는 것은 할당이 아니다.
포인터는 그저 결정되어 있는 고정된 크기의 값이므로, 우리는 스택에 포인터를 저장할 수 있지만, 실제 데이터를 사용하고자 할때에는 포인터를 따라가야 한다.

힙에 저장된 데이터에 접근하는 것은 스택에 저장된 데이터에 접근하는 것 보다 느린데, 그 이유는 포인터가 가리킨 곳을 따라가야 하기 때문이다. 내 코드의 어떤 부분이 힙의 어떤 데이터를 사용하는지 추적하는 것, 힙 내의 사용하지 않는 데이터를 제거하여 공간의 효율성을 달성하는 것 모두 소유권과 관련된 문제이다.

소유권 규칙

1.Rust의 각 값은 해당값의 오너(Owner)라고 불리우는 변수를 가지고 있다.
2. 한번에 딱 하나의 오너만 존재할 수 있다.
3. 오너가 스코프 밖으로 벗어난다면, 해당 값은 dropped 되어진다. (버려진다.)


{					// s 변수는 유효하지 않다.
	let s = "hello"	// s 변수는 이 지점부터 유효하다.
}					// 스코프가 끝났으므로 s는 유효하지 않다.

Move

Rust에서는 값을 변수에 배정하거나, 값을 함수에 전달하거나, 값을 함수에서 반환하거나 하는 식의 연산이 일어날 때 대부분 그 값이 복사되지 않고 이동된다. 값의 원주인은 값의 소유권을 새로운 주인에게 양도하고 미초기화 상태, 즉 더 이상 유효하지 않다고 간주한다.

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

println!("{}, world", s1);

해당 코드는 실행되지 않는데, 그 이유는 Rust의 컴파일러가 친절하게 설명해준다.
변수 s1의 값은 String으로 이 데이터는 Heap에 존재한다. 새로운 변수 s2를 선언하여 s1의 값을 전달하였다. (value moved here), 해당 코드가 실행되게 하려면 s1에 clone()이라는 메소드를 사용하여 Heap 데이터를 깊이 복사하여야 한다.

또한 위와같은 컴파일 에러 메시지를 통해 GC(가비지 컬렉션) 없이도 문자열이나 벡터의 내용 해제 시점을 알 수 있다. 주로 이동되는 타입은 벡터, 문자열 처럼 메모리 사용량이 많고, 복사하는데 비용이 큰 타입인 경우이다.

Copy

Rust의 Copy 타입은 값이 이동하지 않고 복사가 되는데, 앞어 String 처럼 Heap에 있는 데이터를 가리키는 포인터가 이동하는 것이 아니라, 완전히 독립된 복사본이 만들어진다. float, char, bool 등이 여기에 포함된다. Copy 타입의 tuple, fixed array 또한 포함된다.

struct Label { number: i32 }

fn print(l: Label) {
    println!("{}", l.number);
}

fn main() { 
    let l = Label{number: 3};
    print(l);
    println!("{}", l.number); /* Error
    borrow of moved value: `l` value borrowed here after move */
}

기본적으로 struct와 enum은 copy 타입이 아니다. Label은 copy가 아니므로 print에 전달하면, 소유권이 print에 넘어가고 결국 println!에서 사용되기전에 이미 드롭되어 버린다.

#[derive(Clone, Copy)]
struct Label { number: i32 }

fn print(l: Label) {
    println!("{}", l.number);
}

fn main() { 
    let l = Label{number: 3};
    print(l);
    println!("{}", l.number);
}

위와 같이 #[derive(Copy, Clone)] 어트리뷰트를 사용하면 Copy 타입으로 만들 수 있다.

그러나 Copy 타입이 아닌 타입으로 된 필드를 구조체가 가지고 있다면 이는 불가능하다.

#[derive(Copy, Clone)]
struct School { name: String }
// error: the trait `Copy` may not be implemented for this type
profile
꾸준하게

0개의 댓글