타 언어에서 rust library 사용하기

 

일반적으로 시스템 라이브러리라함은 네이티브 언어로 작성되었음을 의미한다. 그리고 보통 네이티브 언어는 c/c++과 같은 언어를 의미한다. 시스템 라이브러리는 다른 언어로 작성한 프로그램에서도 이용할 수 있어야 한다. 그래서 많은 프로그래밍 언어들이 c/c++로 작성된 라이브러리를 사용하기 위한 패키지(이하 ffi 패키지)를 제공한다. java의 jni (java native interface), python의 ctype, ruby와 javascript의 ffi (foreign function interface) 등이 바로 그 것이다. 물론 rust에서도 c 라이브러리를 사용하기 위해 ffi 패키지를 제공한다.

rust는 c/c++과 마찬가지로 (다른 용도로도 사용할 수 있지만 기본적으로는) 시스템 프로그래밍을 위한 언어다. 따라서 rust로 작성된 라이브러리도 다른 언어에서 사용할 수 있어야 한다. 이를 위해 rust는 c 라이브러리와 같은 구조의 동적 라이브러리를 만들 수 있는 옵션을 제공한다.

 

 

동적 라이브러리 만들기

rust는 cargo라는 패키지 매니저가 많은 것들을 대신 해주기 때문에 c 라이브러리를 만드는 것과는 비교도 할 수 없을 정도로 편하다. c로 소스 코드를 작성하고 소스 코드만큼 많은 양의 makefile을 작성하던 과거의 나는 이제 안녕!

…은 개뿔. 프로그램이 커지고, 그래서 모듈이 많아지고, 따라서 디펜던시가 증가하고, 그로 인해 옵션이 많이지면 cargo도 복잡하긴 매한가지다. 너무 좋아하진 말자.

언어를 배운다는 것은 Hello, world! 를 출력하는 방법을 배우는 것이다. 그러니까 Hello, world! 를 출력하는 rust 라이브러리를 만들어 보자. 싫다면 다른 라이브러리를 알아서 만들어 보길 추천한다. 예를 들어 알파고알파고 라거나 알파고 같은 것 말이다.

일단 cargo를 이용해 새 패키지를 생성한다.

$ cargo new hello
$ cd hello
$ tree
.
├── Cargo.toml
└── src
    └── lib.rs

우리의 요구사항은 Hello, world! 를 출력하는 것이다. 요구 사항을 만족하는 소스 코드를 lib.rs 파일에 입력한다.

fn hello() {
    println!("Hello, world!");
}
$ cargo build
    Compiling hello v0.1.0 (file:///Users/arzhna/dev/hello)
src/lib.rs:1:1: 3:2 warning: function is never used: `hello`, #[warn(dead_code)] on by default
src/lib.rs:1 fn hello() {
src/lib.rs:2     println!("Hello, world!");
src/lib.rs:3 }  

그대로 빌드하니 dead_code 어쩌고 하는 무시무시한 경고 메시지를 출력하며 컴파일이 됐다. 경고 메시지는 뭔지 잘 모르겠으니 무시하고, 라이브러리가 잘 만들어졌나 확인해보았다.

$ ls target/debug/lib*
target/debug/libhello.rlib

이럴 수가! 확장자가 rlib 이다. rlib은 rust 정적 라이브러리 파일의 확장자다. ffi 패키지들은 so 또는 dylib 이라는 확장자를 가진 동적 라이브러리를 지원한다. python에서 제공하는 ctype의 c 라이브러리 로드 클래스는 이름부터 cdll(c dynamic link library)이다. rlib은 못 쓴다는 뜻이다.

 

살려야 한다! 동적 라이브러리를 만들어야 한다!

 

동적 라이브러리를 만들기 위해서는 동적 라이브러리를 만들기 위한 옵션을 설정해야 한다. Cargo.toml 파일을 열고 적당한 위치에 아래와 같이 이 패키지는 동적 라이브러리여야만 해!라고 명시하기 위한 [lib] 섹션을 추가하자.

[lib]
crate-type = ["dylib"]

그리고 다시 빌드해보자.

$ cargo build
    Compiling hello v0.1.0 (file:///Users/arzhna/dev/hello)
src/lib.rs:1:1: 3:2 warning: function is never used: `hello`, #[warn(dead_code)] on by default
src/lib.rs:1 fn hello() {
src/lib.rs:2     println!("Hello, world!");
src/lib.rs:3 }
$   
$ ls target/debug/lib*
target/debug/libhello.dylib

(역시 경고는 무시하고) 확인해보니 이번에는 dylib 이라는 확장자를 가진 동적 라이브러리가 잘 생성되었다. 만세!

 

그렇다면 이대로 쓰면 될까? 경고가 찝찝한데?

 

메시지를 다시 살펴보니 function is never used 라고 경고하고 있다. hello() 함수를 어디서도 사용하고 있지 않고 있기 때문이다. 아무도 사용하지 않는 dead_code 가 있는 경우 경고 메시지를 발생시키는 것이 rust 컴파일러의 기본 설정이다.

라이브러리가 제공하는 API 함수라면 라이브러리 내에서 사용하고 있지 않아도 전혀 이상할 것이 없는데 왜 경고 메시지가 나올까? 혹시 API 함수가 아니라면? 그렇다. 처음에 작성했던 hello() 함수는 로컬 함수였다. 외부에서 사용할 수 있도록 선언되지 않았기 때문에 경고를 발생시킨 것이다. 로컬 함수를 API 함수로 바꾸기 위해서는 몇가지 어노테이션을 추가해야 한다.

 

pub extern fn hello() {
    println!("Hello, world!");
}

pubextern 이라는 어노테이션이 추가되었다. pub은 public 함수임을 선언하기 위해, extern은 외부에서도 사용할 수 있음을 선언하기 위해 붙이는 어노테이션이다.

 

이제 외부에서 사용할 수 있는 동적 라이브러리를 만들 수 있을까? 아직이다. 대부분의 컴파일러는 컴파일 과정에서 네임 맹글링(name mangling)이라는 작업을 수행한다. 정의되어 있는 함수명을 정해진 규칙에 의해 변경하는 작업이다. 그런데 불행히도 언어마다 맹글링 규칙이 다르다. c와 c++을 혼용해 사용하는 프로그램에서는 extern "C" {...}와 같은 코드들을 흔하게 볼 수 있다. 이는 c와 c++의 맹글링 규칙이 다르기 때문에 c 스타일로 맞춰주기 위한 코드다.

c 라이브러리를 로드하는 패키지들은 c 스타일의 맹글링만을 고려한다. 따라서 rust 컴파일러가 c 스타일의 맹글링을 하도록 해야한다. 다음과 같이 #[no_mangle] 어노테이션을 추가해 rust 컴파일러의 맹글링 스위치를 꺼버리자.

#[no_mangle]
pub extern fn hello() {
    println!("Hello, world!");
}

이제 다른 언어에서 사용할 수 있는 동적 라이브러리를 생성할 준비가 끝났다. 빌드하자.

 

$ cargo build
   Compiling hello v0.1.0 (file:///Users/arzhna/dev/test/hello)
$ ls target/debug/lib*          
target/debug/libhello.dylib

경고 메시지도 없고 동적 라이브러리도 제대로 생성되었다.

신난다!

easy

 

 

rust 동적 라이브러리 사용하기

서두에서 언급했던 바와 같이 대부분의 언어들은 c/c++ 라이브러리를 사용하기 위한 패키지를 제공한다. 이전 섹션에서 만든 rust 라이브러리는 c 스타일로 컴파일된 동적 라이브러리다. 따라서 c/c++ 라이브러리를 사용하기 위한 패키지를 그대로 이용할 수 있다.

python

파이썬은 ctype이라는 패키지를 제공한다. ctype의 cdll이라는 클래스를 통해 c/c++ 라이브러리를 로드할 수 있다. 백문이 불여일견! 바로 테스트해보자.

# hello.py

from ctypes import cdll

# libhello 라이브러리를 로드한다.
lib = cdll.LoadLibrary("./target/release/libhello.dylib")

# libhello 라이브러리가 제공하는 hello() api를 호출한다.
lib.hello()

print("done!")

실행하면?

$ python hello.py
Hello, world!
done!
$

굿잡!

 

c

c에서 rust 라이브러리를 사용하기 위해서는 헤더 파일이 필요하다. rust 라이브러리가 제공하는 API들의 프로토타입을 c 스타일로 선언해주면 오케이!

// hello.h

void hello(void);

 

헤더 파일이 생겼으니 이제 소스 코드를 작성하고, c 라이브러리 링크하던 대로 옵션 주고, 그렇게 하던 대로 빌드하면 된다.

// hello.c

#include <stdio.h>
#include "hello.h"

int main(){
    hello();
    return 0;
}
$ gcc -lhello -L./target/release/ -I. hello.c -o hello
$ ./hello 
Hello, world!

성공!

 

swift
swift는 objective-c를 기반으로 만들어져 있다. 당연히 c와 호환성이 우수하다. rust로 작성된 라이브러리지만 c 라이브러리와 다를바 없으므로 Bridging-Header만 만들면 그대로 사용할 수 있다.

 

java
자바는 jni를 만들면되는데… 귀찮으니 패스하자.

 

ruby
안타깝게도 나는 ruby 언어를 모른다. ffi 패키지가 있다카더라.

 

js
역시 ffi를 이용하면 된다던데?

 

go


불행히도 그나마 할 줄 아는 언어가 c, python, java 밖에 없다. 그러니 예제는 여기서 끝!

 

 

RUST 소개

rust러스트모질라재단에서 “안전하고, 병렬적이며, 실용적인” 언어라는 컨셉으로 디자인한 차세대 프로그래밍 언어이며 오픈소스로 개발이 진행중이다. #이상한모임에서 우연히 접하고 흥미가 생겨 짬짬히 스터디 중이다.

첫인상은 C/C++과 비슷하다. 유사한 키워드와 유사한 신택스를 사용한다. 물론 시맨틱은 크게 다르다고 한다. 아직은 접한지 얼마 되지 않아 알 수 없지만…

안전한 프로그래밍을 위해 메모리 에러를 컴파일 타임에 발생시키도록 한 점은 C/C++ 개발자 입장에서 몹시 환영할만한 점이다. 어디서 발생하고 있을지 모를 릭과 바이얼레이션을 찾기 위해 코드 더미를 어슬렁대는 하이에나와 같은 디버깅은 더이상 하지 않아도 된다! 만세!

 

Install

설치는 쉽다. 공식 홈페이지에서 바이너리를 받아도 되고 github에서 소스를 받아 빌드해 사용할 수도 있다. 리눅스/맥 유저라면 다운로드 & 설치까지 한방에 해결하는 스크립트를 제공하기도 한다.

$ curl -s https://static.rust-lang.org/rustup.sh | sudo sh

 

Hello World

역시 모든 언어의 기본은 Hello World!

 

다음과 같이 컴파일 하면 elf파일이 생성된다. 실행하면 Hello World!

$ rustc hello.rs
$ ./hello
Hello World!

 

튜토리얼

다음의 사이트에서 튜토리얼과 예제를 제공한다. 아직 다 보진 못했지만 C/C++과 비슷해서 다른 차세대 언어들에 비해 쉽게 이해할 수 있는 것 같다.

https://www.penflip.com/sarojaba/rust-doc-korean

http://rustbyexample.com/hello.html

 

 

그래서?

작년엔 swift 공부한다고 설쳤는데 결국 남은게 없다. rust 역시 공부는 하더라도 사용하지 않는다면 똑같은 결말을 볼 것 같다. 혼자서는 뭔가 아이템을 생각해내질 못해서 결국엔 흐지부지 중단해버리고 만다. 생각해보건데 글감에 대한 고민의 연장선 상에 있는 것 같다. 글감에 대한 고민은 어느 정도 강제적인 훈련을 통해 해결이 된 것 같지만 프로그래밍 언어는 과연 이 고민을 해결 할 수 있을까?