C++에서 Rust로 넘어가기 위한 C++ 개발자들을 위한 개념들을 설명합니다.
항상 맨처음에 "개념" 하나씩 쓰고 시작하는데 C++에서 Rust로 넘어가는데 알아야할 명제 입니다.

C++의 class ( struct )와 비슷한 개념이 rust의 struct입니다. 이 두개를 비교하면서 Rust로 열심히 넘어가 보겠습니다.

 

개념 : "C++의 class (struct)의 거의 모든 개념은 Rust의 struct로 구현 가능하다."

 

일단 바로 예제 코드 갑니다.

 

아래 예제는 

  • static function에서 FileWriter를 create하여 instance를 생성합니다.
  • create() 함수에서 파일을 엽니다.
  • write() 함수로 내용을 씁니다.
  • 소멸자에서 파일을 닫습니다.
C++ Rust
#include <iostream>
using namespace std;


struct FileWriter{
    FILE * fp;
    
    static FileWriter create();
    void write();
    void read();
    ~FileWriter();
};


FileWriter FileWriter::create(){
    FILE * fp  = fopen("test.txt", "wt");
    cout << "file opened" << endl;
    return FileWriter{ fp = fp };
}


void FileWriter::write(){
    fprintf(fp, "test data");
    cout << "written data" << endl;
}


FileWriter::~FileWriter(){
    fclose(fp);
    cout << "file closed" << endl;
}




int main(int argc, const char * argv[]) {
    auto writer = FileWriter::create();
    writer.write();
    
    return 0;
}
use std::ffi::CString;


struct FileWriter{
    fd: *mut libc::FILE
}


impl FileWriter {
    fn create() -> FileWriter{
        let fd: *mut libc::FILE = unsafe {
            let file_name = CString::new("test.txt").unwrap();
            let file_attr = CString::new("w").unwrap();


            libc::fopen(file_name.as_ptr(), file_attr.as_ptr())
        };


        FileWriter{ fd }
    }
    fn write(&mut self){
        unsafe {
            let data = CString::new("test data").unwrap();
            libc::fprintf(self.fd, data.as_ptr())
        };
    }
}


impl Drop for FileWriter{
    fn drop(&mut self) {
        unsafe {
            libc::fclose(self.fd);
        }
    }
}


fn main() {
    let writer = FileWriter::create();
    writer.write();
}
file opened
written data
file closed

file opened
written data
file closed

rust예제의 경우 좀 억지감이 있지만 C++과 똑같이 만들기 위해 좀 억지를 부렸습니다.

머 여기서 좋은 점도 알게 되었습니다. "Rust 에서는 왼만한 C/C++ 함수들을 바로 가져다 쓸 수 있구나"

 

이 예제에서 알 수 있는것들은

  • rust struct는 method ( member function ) 이 없다.
  • impl를 통하여 method를 구현한다. ( c++의 class::method 패턴과 비슷하지요? )
  • rust는 생성자가 없어서 factory pattern을 사용한다.
  • rust을 Drop trait를 구현하여 소멸자 동작을 구현 할수있다.

 

다음번에는 Rust의 에러처리에 대해 알아보겠습니다.

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를 비교해 보겠습니다.

+ Recent posts