profile image

L o a d i n g . . .

article thumbnail image
Published 2023. 8. 31. 19:03
728x90

Rust는 다른 언어에서 찾아보기 힘든 ownership이란 개념이 존재합니다. Java의 경우 garbage collector가 메모리를 관리하고 C/C++의 경우 프로그래머가 메모리를 직접  관리합니다. Rust는 ownership을 활용해 garbage collector와 프로그래머의 개입 없이도 메모리를 효과적으로 관리할 수 있습니다(여기서 말하는 메모리의 관리는 사용하지 않는 객체 등에 할당된 메모리를 해제하는 것을 의미합니다). 이번 포스팅에서는 Rust가 제공하는 ownership의 4가지 유형에 대해 살펴보겠습니다. 

 

Move 

Move는 ownership을 한쪽 위치에서 다른 쪽 위치로 옮기는 것을 의미합니다. 

fn main() {
    let string = String::new();
    use_string(string);
    println!("{}", string);
}

fn use_string(s: String) {
}

위 코드에서 main 함수에서 use_string 함수를 호출 시 "string"에 대한 ownership이 main 함수에서 use_string 함수로 이동하게 됩니다. 하지만 main 함수는 use_string 함수를 호출한 이후 println를 통해 "string" 변수를 출력하려고 하는데요, ownership이 이미 use_string으로 옮겨갔기 때문에 컴파일 타임에 에러가 발생합니다. 

Ownership move로 인해 컴파일 에러 발생

그럼 위 코드를 어떻게 동작하도록 만들 수 있을까요? Ownership을 move 하는 게 아닌 copy, 즉 복사 방식을 활용하면 됩니다. 

 

Copy 

Copy는 move와 다르게 값을 복사함으로써 move에서 발생하는 ownership 문제를 회피할 수 있습니다. 다만 copy는 Copy trait을 지닌 타입에 대해서만 지원됩니다. 

fn main() {
    let number = 10;
    use_number(number);
    println!("{}", number);
}

fn use_number(number: u32) {}

use_number 함수에 인자로 전달된 "number"는 인자로 그대로 전달되는 게 아니라 복사돼서 새로운 값으로 전달됩니다. 만약 인자로 전달되는 객체의 크기가 크면 복사의 비용도 커지게 됩니다. Move에서의 ownership 문제는 마주치지 않으면서도, copy로 인한 복사 비용을 줄일 수 있는 방법은 없을까요? 바로 borrow 방식을 사용하면 가능합니다. 

 

Borrow (reference) 

Borrow는 "C/C++의 reference를 넘기는 방식"과 "Read/Write lock에서의 read lock"을 합친 개념 유사합니다. 무슨 말인지는 아래 코드를 보면서 이해해 보겠습니다. 

fn main() {
    let string = String::from("hello");
    borrow_string(&string);
    println!("inside main {}", string)
}

fn borrow_string(s: &String) {
    println!("inside borrow_string {}", s)
}

위 코드에서 borrow_string 함수는 "string"에 대한 ownership을 일시적으로 빌리게 됩니다(borrow). 이 경우 "string"에 대한 참조(reference)를 넘겨주는데요, 이 부분이 C/C++에서 reference를 넘겨주는 부분과 유사합니다. 그리고 rust는 수정이 발생한 지 않는 한 "string"에 대한 ownership을 동시에 여러 번 빌릴 수 있도록 허용합니다. 이는 "Read/Write lock"를 사용하는 시스템에서 특정 데이터에 대해 read lock을 다수 획득할 수 있는 것과 유사합니다. 

 

Borrow (mutable reference) 

이름에서 알 수 있듯이 수정 가능한 객체의 reference를 빌리는 방식입니다. 일반적인 borrow와 달리 객체를 수정할 수 있습니다. 하지만 mutable reference는 동시에 한 번만 빌릴 수 있습니다. 

fn main() {
    let mut vec = vec![1,2,3];
    let first = &mut vec[0];
    let second = &mut vec[1];
    println!("first: {}", first);
}

위 코드를 보면 "first"와 "second"에서 mutable reference를 빌리는 것을 확인할 수 있습니다. 중복으로 mutable reference를 빌리면 아래와 같은 에러가 발생합니다. Rust는 mutable reference를 빌리게 되면 그 어떤 reference를 빌릴 수 없도록 언어 차원에서 방지하기 때문에 에러가 발생하게 됩니다. 이러한 방식의 장점은 컴파일 타임에 data race(하나의 데이터에 동시에 접근하여 데이터를 수정함으로써 발생하는 문제)를 방지할 수 있다는 점입니다. 

Mutable reference

 

Summary 

이번 포스팅을 통해 rust가 메모리를 관리하기 위해서 언어적 차원에서 제공하는 기능에 대해 살펴봤습니다. 새로운 언어를 배울 때 가장 큰 재미는 해당 언어가 도입한 아이디어를 이해했을 때라고 생각합니다. 다른 언어에서 보기 어려운 rust의 다양한 기능은 참 흥미로운 것 같습니다.

728x90
복사했습니다!