Bullshitting Blog

개소리하는 블로그

Rust의 개꿀 라이브러리들

Tags = [ rust ]

개인적으로 개발을 할 때, 당장 어떻게든 만들어내는건 어렵지 않게 하지만, 개발 도구를 맛깔나게 활용하지는 않는 편이다. 그냥 '요래요래 하면 될거같은데?' 하고 손부터 가는 타입. 그래서 개꿀 라이브러리를 놓치고 깡으로 손으로 짜는 경우가 많다.

그러던 중, CTO가 관리하던 코드를 물려받게 되었다. 코드가 아름답다고 한다면 이런 것일 것이다. LSTM + 강화학습 모델의 학습 인프라를 구성하는 코드리포인데, 시뮬레이션은 기본, 데이터 프로세싱까지 모두 담겨있다. 마이크로서비스를 섣불리 도입해서 삽질을 하지도 않고 바로 모노리포 구조로 잘 짜여 있었다. 여튼 개쩐다는 표현은 대충 이쯤에서 마무리하고...

structopt: Argument를 편리하게 받자

일단 난 CLI를 더 선호한다. 그래서 왠만하면 CLI로 툴을 만드는 편이다. 그런데, bash scripting을 하는 경우가 많아서, argument를 가져올 때 손이 알아서 아래와 같이 입력한다.

if [ $# -ne 2 ]; then
    echo "Usage: $0 <hello> <world>"
    exit -1
fi
HELLO=$1
WORLD=$2

그리고 이 습관은 rust까지 따라왔다.

fn main() {
    let args = std::env::args().collect::<Vec<String>>();
    if args.len() != 3 {
        eprintln!("Usage: {} <hello> <world>", args[0]);
    }
    let hello = &args[1];
    let world = &args[2];
}

그리고 난 CTO가 물려준 코드에서 structopt를 발견했다. 아 물론, 다른 마이크로서비스에도 있었는데, 내 담당은 아니라 대충 넘겼었긴 했다. structopt를 이용하면 아래와 같이 이용할 수 있다.

use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(name = "opt", about = "hello world")]
struct Opt {
    #[structopt(short, long)]
    hello: String,
    #[structopt(short, long)]
    world: String,
}

fn main() {
    let Opt { hello, world } = Opt::from_args();
}

코드 줄 수는 별다른 차이가 없어 보일 수 있지만, eprintln이 사라진 것을 볼 수 있다. argument를 입력받기 위한 안내를 굳이 내가 입력하지 않아도 되는 것이다. 게다가 커맨드라인 상에서 더 깔끔하게 나온다. 추가적으로, Opt struct에서 타입을 정의했다. 자료형에 대해 걱정할 필요가 없이 가져다 쓰면 된다.

serde: 데이터 파싱? 직렬화? 그냥 add하자

이 라이브러리는 내부 프로토콜로서 프로토버프를 이용하면서 예전부터 이용하고 있었다. 꽤 깔끔하게 구조체를 표현할 수 있어서 좋다 정도였고, api 클라이언트를 만들 때, 사용이 아주 권장되지만, 개인적으로 작업을 진행할 때는 귀찮다고 대충 손으로 써내려가면서 만들어 왔다. 하지만, API 클라이언트를 만드는 일이 생각보다 많다. 당장, 개인적으로 작업중인 한국투자증권OpenAPI, open-banking-api만 해도 API 클라이언트다. 또한 회사에서 메시지큐의 공식 라이브러리에서 지원하지 않는 Admin API를 날리는 라이브러리도 개발했는데, 이 또한 API 클라이언트이다. 그리고 이 클라이언트가 날린 리퀘스트에 대한 리스폰스를 파싱하기 위해서는... 그냥 serde 쓰자. 나처럼 손으로 다 빚어내지 않았음 한다 ㅋㅋㅋㅋㅋㅋ. 체대 아니랄까봐 신체의 스피드로 개발속도를 내고 앉아있다. 개인적으로 불편함이 어느정도 쌓여야 해소를 위해 움직이기 때문에, open-banking-api를 작업하면서 serde를 도입하게 되었다. 물론 한국투자증권API에도 serde 라이브러리를 add해놓긴 했지만, 사용해야 할 모든 곳에 제대로 사용하지는 않고 있다. 조만간 바꿔야지.

serde는 데이터의 serialize, deserialize를 지원하는 라이브러리다. derive를 통해 쉽게 serialize/deserialize 매서드를 가져올 수 있다. 만약 이게 없으면 아래와 같이 작성해야 한다.

use json::json;

fn main() {
    // deserialize
    let raw = b"{\\"hello\\":1,\\"world\\":\\"universe\\"}";
    let parsed_json = json::parse(&raw).unwrap();
    let mut hello = 0;
    let mut world = String::new();
    match parsed_json {
        json::JsonValue::Object(o) => {
            match o.get("hello") {
                Some(hello) => {
                    match hello {
                        json::JsonValue::Short(n) => {
                            let (positive, mantissa, exponent) = n.as_parts();
                            hello = positive as i64 * mantissa as i64 * 10i64.pow(exponent as u32);
                        },
                    }
                }
                None => todo!(),
            };
            match o.get("world") {
                Some(world) => {
                    match world {
                        json::JsonValue::String(s) => {
                            world = s.to_owned();
                        },
                    }
                }
                None => todo!(),
            }
        },
        _ => {
            todo!();
        },
    }

    // serialize
    let hello = 1;
    let world = "universe".to_string();
    {% raw %}
    let serialized_message = format!("{{\\"hello\\":{},\\"world\\":\\"{}\\"}}", hello, world);
    {% endraw %}
}

json::parse가 모든 것을 꼬아버렸다. 아주 귀찮다. 이걸 serde와 serde_json을 이용하면 아래와 같이 간단하게 해결된다.

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct ParsedJson {
    pub hello: u8,
    pub world: String,
}

fn main() {
    // deserialize
    let raw = b"{\\"hello\\":1,\\"world\\":\\"universe\\"}";
    let parsed_json: ParsedJson = serde_json::from_slice(raw).unwrap();

    // serialize
    let message = ParsedJson {
        hello: 1,
        world: "universe".to_string(),
    };
    let serialized_message = serde_json::to_string(&message).unwrap();
}

serde 만세