C++ 에서 Rust 로 넘어가기 (c++ vs rust, Ownership)
C++에서 Rust로 넘어가기 위한 C++ 개발자들을 위한 개념들을 설명합니다.
항상 맨처음에 "개념" 하나씩 쓰고 시작하는데 C++에서 Rust로 넘어가는데 알아야할 명제 입니다.
Rust의 주요 특징중에 "소유권 이전"이 최대의 특징으로 꼽힐껍니다. 이걸 C++과 비교 해 보겠습니다.
개념 "C++은 Copy가 기본 동작이고 Rust는 Move가 기본동작이다."
Primitive
C/C++, Java, Scala, Rust 거의 모든 지상의 언어들을 통털어서 copy가 기본이고 rust 역시도 copy가 기본입니다.
C++ | Rust |
int main(int argc, const char * argv[]) { auto a = 10; auto b = a; auto c = a; return 0; } |
fn main() { let a = 10; let b = a; let c = a; } |
a라는 변수에 10을 넣은 후 b,c라는 변수에 동시에 넣었지만 정상 동작한다.
Object
C/C++은 Copy, Reference, Move가 가능하고 Java는 무조건 Reference지만 Rust는 Move기 기본입니다.
C++ | Rust |
int main(int argc, const char * argv[]) { auto a = 10; auto b = a; auto c = a; return 0; } |
fn main() { let a = 10; let b = a; let c = a; } |
[SUCESS] | 3 | let a = String::from("string"); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait 4 | let b = a; | - value moved here 5 | let c = a; | ^ value used here after move |
C++은 b, c에 a가 copy되어 문제없이 들어가지만 Rust는 a의 소유권이 b로 넘어간후 a를 다시 사용하였기 때문에 에러가 발생합니다.
여기까지는 다른 블로그에서도 많이 하는 얘기고.. C++을 Rust와 같이 바꿔 보겠습니다.
C++ | Rust |
int main(int argc, const char * argv[]) { auto a = std::unique_ptr<std::string>(new std::string("string")); auto b = std::move(a); auto c = a; return 0; } |
fn main() { let a = 10; let b = a; let c = a; } |
Call to implicitly-deleted copy constructor of 'std::unique_ptr<std::string>' | 3 | let a = String::from("string"); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait 4 | let b = a; | - value moved here 5 | let c = a; | ^ value used here after move |
아시는바와 같이 std::unique_ptr은 copy operator를 삭제하여 copy동작을 막은 템플릿입니다. 소유권이 하나의 인스턴스에만 유지하도록 하는 helper class입니다.
Rust은 기본이 unique value이므로 위와 같이 하면 rust와 똑같이 동작합니다.
C++만 Rust형으로 바꿔보는건 재미가 없으니 Rust를 C++로도 바꿔 보겠습니다. 일단 rust에서만 에러나는 케이스입니다
C++ | Rust |
struct testStruct{ int value; }; int main(int argc, const char * argv[]) { testStruct a = { 10 }; auto b = a; auto c = a; return 0; } |
struct TestStruct{ a:u16 } fn main() { let a = TestStruct{ a: 10 }; let b = a; let c = a; } |
[SUCESS] | 7 | let a = testStruct{ a: 10 }; | - move occurs because `a` has type `TestStruct`, which does not implement the `Copy` trait 8 | let b = a; | - value moved here 9 | let c = a; | ^ value used here after move |
당연하게도 struct도 rust는 move가 기본이기에 move된 변수를 사용하였다는 에러가 발생합니다.
testStruct가 move가 기본이기 때문입니다. testStruct의 기본동작을 copy로 바꿔봅시다.
C++ | Rust |
struct testStruct{ int value; }; int main(int argc, const char * argv[]) { testStruct a = { 10 }; auto b = a; auto c = a; return 0; } |
#[derive(Copy, Clone)] struct TestStruct{ a:u16 } fn main() { let a = TestStruct{ a: 10 }; let b = a; let c = a; } |
[SUCESS] | [SUCESS] |
Rust도 Copy함수를 구현했더니 C++처럼 동작합니다.
정리
간단히 rust의 의도를 파악해 보자면
- object의 기본동작으로 move이므로 memory deallocation 시점을 정확히 잡을수있다.
- 의도하지 않는 copy가 일어나서 memory가 낭비되지 않도록한다 ( copy, clone이 필요할때는 의도적으로 coding 해라 )
사실 처음에 코드를 작성할때는 헷갈렸지만 자꾸 작성하다보면 변수의 scope이 심각하게 명확하고 function, thread등에서 공유되는 변수들의 범위와 scope가 정말 명확하게 보입니다( 그렇기 때문에 의도하지 않은 변수공유, 스코프가 벗어난 변수 참조등을 원천적으로 막아 안전한 프로그래밍이 가능합니다)
다음시간에는 C++ struct (class)와 Rust struct를 비교해 보겠습니다.