
Rust의 구조체를 Python의 클래스와 연동하는 예제다.
많은 경우 우리는 데이터를 단일 변수가 아니라 연관된 덩어리로 관리하기 때문에
Rust로 작성한 로직을 Python에서 사용하고자 할 때 반드시 알아야 할 내용이다.
~/workspace$ mkdir struct-to-class && cd struct-to-class ~/workspace/struct-to-class$ python3 -m venv venv ~/workspace/struct-to-class$ source venv/bin/activate ~/workspace/struct-to-class$ pip install maturin fastapi uvicorn ~/workspace/struct-to-class$ maturin init ~/workspace/struct-to-class$ # 선택지 중 기본값인 PyO3 선택 ~/workspace/struct-to-class$ # Cargo.toml과 src/lib.rs가 자동 생성된다 ~/workspace/struct-to-class$ # Python 코드는 직접 생성해 주어야 한다 ~/workspace/struct-to-class$ mkdir app && touch app/main.py ~/workspace/struct-to-class$ tree -I venv . ├── app │ └── main.py ├── Cargo.toml ├── pyproject.toml └── src └── lib.rs
Cargo.toml 파일을 열어 라이브러리 이름을 수정해 주겠다.
Cargo.toml[package] name = "struct-to-class" version = "0.1.0" edition = "2024" [lib] name = "rust_engine" crate-type = ["cdylib"] [dependencies] pyo3 = "0.28.0"
Python에서 클래스로 사용하고자 하는 구조체에는 #[pyclass] 속성을 붙인다.
#[pyo3(get)] 속성이 붙은 값은 Python에서 별도의 함수 없이 직접 불러올 수 있으며
#[pyo3(set)] 속성이 붙은 값은 Python에서 별도의 함수 없이 직접 입력할 수 있다.
Python에서 클래스의 메서드로 사용하고자 하는 구조체 구현에는 #[pymethods] 속성을 붙인다.
#[new] 속성이 붙은 함수는 생성자로 사용되며 Python의 __init__ 역할을 한다.
구조체도 함수와 마찬가지로 #[pymodule] 속성이 붙은 함수에서 등록해야 사용할 수 있다.
src/lib.rsuse pyo3::prelude::*; #[pyclass] struct Counter { #[pyo3(get)] value: i64, } #[pymethods] impl Counter { #[new] fn new(initial_value: i64) -> Self { Counter { value: initial_value } } // 이하 Python에서 호출할 메서드 fn increment(&mut self, amount: i64) { self.value += amount; } fn get_value(&self) -> i64 { self.value } } #[pymodule] fn rust_engine(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::<Counter>()?; Ok(()) }
Rust에서 구조체로 정의한 클래스의 인스턴스를 만들어 사용한다.
app/main.pyfrom fastapi import FastAPI import rust_engine app = FastAPI() my_counter = rust_engine.Counter(100) @app.get("/counter") def read_counter(): return { "current_value": my_counter.value } @app.get("/counter/add/{amount}") def add_to_counter(amount: int): my_counter.increment(amount) return { "added": amount, "new_value": my_counter.get_value(), "type_from_rust": str(type(my_counter)) }
Maturin 라이브러리를 통해 Rust 코드를 Python에서 호출 가능한 형태로 컴파일한다.
컴파일 후 pip list 명령어를 사용해 보면 Cargo.toml 파일에 작성한 패키지 이름을 확인할 수 있다.
uvicorn 라이브러리를 통해 FastAPI를 실행한다.
~/workspace/struct-to-class$ maturin develop ~/workspace/struct-to-class$ uvicorn app.main:app --reload
curl 명령어 또는 브라우저를 통해 다음과 같은 테스트를 해볼 수 있다.
http://127.0.0.1:8000/counterhttp://127.0.0.1:8000/counter/add/42http://127.0.0.1:8000/counter/add/-20