Updated rust-inside-other-languages.md

SoonBin authored
revision 70a1ec802f7f2e369854be38418af6f1f37cd2b3
rust-inside-other-languages
# 3.3. 다른 언어에 Rust 포함하기 (Rust Inside Other Languages) - 100%

여기서는, 러스트의 가장 끝내주는 특징 중 하나를 자랑하겠습니다: 런타임 경량화

프로그램 구조가 비대해질수록, 더욱 더 많은 프로그래밍 언어에 의존하게 됩니다. 각 언어들은 서로 다른 장점과 단점을 지니며, 그에 따라 적재적소에 다양한 언어들을 사용하는 폴리글랏(Polyglot)이 주목받게 되었습니다.

많은 언어들의 공통적인 약점은 역시 성능(Performance)입니다. 대체로 높은 생산성을 프로그래머에게 제공하기 위해 언어들은 보다 느려질 수밖에 없으며, 우리는 그에 따른 성능의 손실을 받아들입니다. 이 손실을 줄이기 위해, 시스템의 일부분을 C로 작성하여, C의 코드를 부를 수 있도록 하는 방법이 있습니다. 이를 '외부 함수 인터페이스', 줄여서 'FFI(foreign function interface)'라고 합니다.

러스트는 FFI를 양방향으로 지원합니다: 러스트 안에서 C 코드를 매우 쉽게 호출할 수 있습니다.
게다가 결정적으로, 러스트 또한 C처럼 간단하게 _호출될_ 수 있습니다. 다른 언어 안에서 더 강렬한 무언가를 원할 때, 쓰레기 수집기(GC)가 없고 뛰어난 성능을 가진 러스트와의 결합은 좋은 선택지가 될 수 있습니다.

상세한 정보는 [chapter devoted to FFI][ffi] 에 있지만, 이 챕터에선 루비, 파이썬, 자바스크립트와의 간단한 FFI 사용 예시를 보이겠습니다.

[ffi]: ffi.html

# The problem

많은 다른 예제들을 선택할 수 있지만, 여기서는 다른 언어에 비해 러스트의 이점을 확연히 드러내줄 수 있는 예제들을 택했습니다: 계산 성능과 스레딩.

많은 언어에서는 일관성을 위해, 계산할 메모리들을 스택(stack)보다는 힙(heap)에 위치시킵니다. 특히 객체지향 프로그래밍이나 쓰레기 수집기(GC)를 사용하는 언어들은 힙 할당이 기본적입니다. 때때로 최적화기(optimizer)에 의존하는 것보다 우리가 여러 객체 타입들보다 원시 숫자 타입(primitive number types)들만을 사용한다고 보장하여, 특정 계산들을 스택에 할당하는 것으로 최적화를 달성할 수도 있습니다.

두번째로, 많은 언어들은 많은 상황에서 동시성을 제한하는 '전역 인터프리터 잠금'(GIL, Global Interpreter Lock)을 가집니다. 안전성을 달성한다는 목적에서는 좋은 방향으로 작용하지만, 한번에 처리할 수 있는 일을 제한한다는 점에서 나쁜 작용이 됩니다.

이 두 가지 측면을 드러내기 위해, 작은 프로젝트 하나를 실행해봅니다. 프로젝트 자체보다는 다른 언어 안에서 러스트를 결합하는 것에 집중할 것이기 때문에, 단순한 프로젝트를 사용합시다:

> 스레드 10개로 시작하여, 각 스레드 안에 500만을 카운트합니다. 모든 스레드의 일이 끝나면,
> 'done!'을 출력합니다.

루비로 된 예제는 다음과 같습니다:

```ruby
threads = []

10.times do
threads << Thread.new do
count = 0

5_000_000.times do
count += 1
end

count
end
end

threads.each do |t|
puts "Thread finished with count=#{t.value}"
end
puts "done!"
```

예제를 실행하여, 얼마나 걸리는지 세 봅시다.
컴퓨터 하드웨어 따라, 그 숫자가 변할 것입니다.

제 시스템에서는, 프로그램을 실행시키는데 `2.156`초가 걸렸습니다. 그리고 프로세스 모니터링 도구 중 하나인 `top`을 사용해보면, 제 컴퓨터 중 코어를 하나만 사용하고 있는 것을 확인할 수 있습니다. 이것이 GIL의 정체입니다.
단지 프로그램일 뿐이지만, 눈치 좋은 누군가는 이 문제가 현실 세계에도 일어나는 것을 쉽게 상상해볼 수 있습니다. 그 문제는 바로, 계산들을 도맡아 바쁘게 일하는 스레드가 몇 안된다는 것입니다. 병렬적인 업무에서 계산이 느려지는 이유입니다.

# A Rust library

이 문제를 러스트로 해결해보죠. Cargo에서 새로운 프로젝트를 만듭시다:

```bash
$ cargo new embed
$ cd embed
```

러스트로 간단히 다음과 같이 쓸 수 있습니다:

```rust
use std::thread;

fn process() {
let handles: Vec<_> = (0..10).map(|_| {
thread::spawn(|| {
let mut x = 0;
for _ in (0..5_000_000) {
x += 1
}
})
}).collect();

for h in handles {
println!("Thread finished with count={}",
h.join().map_err(|_| "Could not join a thread!").unwrap());
}
println!("done!");
}
```

지난 예시에서 볼 수 있었던 익숙한 모습이 보이네요. 스레드 10개를 생성하여, `handles` 벡터 안에 넣습니다. 각 스레드 내부에서 한 번 돌때마다 `x` 값을 올리는 루프를 500만번 실행합니다. 그리고 각 스레드를 연결합니다.
하지만 지금은 러스트 라이브러리를 사용했기 때문에, C로부터 호출하는 게 아닙니다. 따라서 이것을 곧바로 다른 언어에서 결합할 수는 없습니다. 그러기 위해서는 2가지의 명시가 필요합니다. 첫번째로, 수정할 코드의 시작은:

```rust,ignore
#[no_mangle]
pub extern fn process() {
```

우리는 새로운 속성인 `no_mangle`을 명시해주어야만 합니다. 러스트 라이브러리를 만들 때, 이것은 컴파일된 결과물에서 함수의 이름을 바꿉니다. 이 튜토리얼에서 이 구문이 스코프 밖에 있는 이유는 다른 언어에서 이 함수를 부르는 방법을 알려주기 위해서입니다. 이 속성은 어떤 명령의 실행과는 관계가 없습니다.
다른 변화된 점은 `pub extern`입니다. `pub`은 이 함수가 모듈 바깥에서 호출될 수 있어야 함을 뜻하고, `extern`은 C로부터 호출될 수 있어야 함을 말해줍니다. 이게 다입니다! 그렇게 바뀌진 않았죠.
두번째로 우리는 `Cargo.toml`의 설정을 변경해주어야 합니다. 맨 밑에 다음을 추가하세요:

```toml
[lib]
name = "embed"
crate-type = ["dylib"]
```

이것은 우리의 라이브러리를 러스트의 표준 동적 라이브러리(standard dynamic library)에 컴파일하길 원한다고 말해줍니다. 기본적으로 러스트는 'rlib'라는 러스트 상세 포맷을 컴파일합니다.

빌드해보죠:

```bash
$ cargo build --release
Compiling embed v0.1.0 (file:///home/steve/src/embed)
```

최적화와 함께 빌드하라는 `cargo build --release` 명령을 사용했습니다. 가능한 이것을 빠르게 하고 싶거든요! `target/release` 안에 라이브러리의 결과물을 볼 수 있습니다.

```bash
$ ls target/release/
build deps examples libembed.so native
```

`libembed.so`는 우리의 '공유 객체' 라이브러리입니다. 이것을 C로 쓰여진 공유 객체 라이브러리처럼 마음껏 사용할 수 있습니다! 플랫폼에 따라, `embed.dll` 또는 `libembed.dylib`가 되기도 합니다.
빌드된 러스트 라이브러리를 손에 넣었으니, 루비에서 직접 사용해봅시다.

# Ruby

`embed.rb`를 열어 다음과 같이 합니다.

```ruby
require 'ffi'

module Hello
extend FFI::Library
ffi_lib 'target/release/libembed.so'
attach_function :process, [], :void
end

Hello.process

puts 'done!'
```

실행하기 전에, 우리는 `ffi` gem을 설치해야 합니다:

```bash
$ gem install ffi # this may need sudo
Fetching: ffi-1.9.8.gem (100%)
Building native extensions. This could take a while...
Successfully installed ffi-1.9.8
Parsing documentation for ffi-1.9.8
Installing ri documentation for ffi-1.9.8
Done installing documentation for ffi after 0 seconds
1 gem installed
```

마침내, 실행해 볼 수 있게 됐습니다:

```bash
$ ruby embed.rb
done!
$
```

와, 정말 빠릅니다! 제 시스템에서는 `0.086`초가 걸렸습니다. 순수한 루비 프로그램이 2초 정도 걸렸는데 말입니다. 이제 코드를 파헤쳐볼까요?

```ruby
require 'ffi'
```

첫번째로 `ffi` gem을 불러옵니다. require를 통해 마치 C 라이브러리처럼 러스트 라이브러리에 접근할 수 있습니다.

```ruby
module Hello
extend FFI::Library
ffi_lib 'target/release/libembed.so'
```

`Hello` 모듈은 공유 라이브러리의 네이티브 함수를 루비에 붙이는 데에 사용합니다. 다음으로 `FFI::Library`를 `extend`할 필요가 있으며, `ffi_lib`를 호출해 공유 객체 라이브러리를 가져옵니다. 우리는 그저 라이브러리가 있는 경로를 넘겨주었습니다. 보는 바와 같이 `target/release/libembed.so` 입니다.

```ruby
attach_function :process, [], :void
```

`attach_function`는 FFI gem에 의해 제공된 메소드입니다. 러스트 안의 `process()` 함수를 같은 이름의 루비 함수와 연결합니다. `process()` 함수는 받는 인자가 없기 때문에 `attach_function`의 두번째 인자는 빈 배열이며, 반환값도 없기 때문에 마지막 인자에 `:void`를 넘깁니다.

```ruby
Hello.process
```

이것이 실제로 러스트 함수를 호출하는 방법입니다. 우리의 `module`과 결합한 다음 `attach_function`에서 연결한 함수를 부릅니다. 마치 루비 함수 같아보이지만 실제로는 러스트입니다!

```ruby
puts 'done!'
```

마지막으로, 우리의 프로젝트가 그랬던 것처럼, `done!`을 출력합니다.
이게 전부입니다! 보았던 대로 두 언어간의 결합이 매우 간단하며, 엄청난 성능을 얻을 수 있습니다.
다음은 파이썬으로 해보죠!

# Python

`embed.py`파일을 만들고 다음의 코드를 입력합니다:

```python
from ctypes import cdll

lib = cdll.LoadLibrary("target/release/libembed.so")

lib.process()

print("done!")
```

아주 쉽죠! `ctypes`모듈로부터 `cdll`을 사용할 수 있습니다. `LoadLibrary`를 호출한 뒤, `process()`를 호출할 수 있습니다.

제가 테스트한 환경에서, `0.017`초가 걸렸군요. 빨라요!

# Node.js

Node는 언어가 아니지만, 현재 가장 주류로 떠오르고 있는 서버사이드 자바스크립트입니다.

Node에서 FFI를 이용하기 위해, 우리는 다음 라이브러리를 설치합니다.

```bash
$ npm install ffi
```

설치 후에는, 다음과 같이 이용합니다:

```javascript
var ffi = require('ffi');

var lib = ffi.Library('target/release/libembed', {
'process': ['void', []]
});

lib.process();

console.log("done!");
```

파이썬 보다는 마치 루비 예제 같네요. 우리는 `ffi` 모듈을 객체로 받아온 뒤, 그 객체를 통해 `ffi.Library()`에 접근합니다. 리턴 타입과 인자 타입을 명시할 필요가 있는데, 이 예제에서는 리턴 타입으로 `'void'` , 인자가 없다는 것을 뜻하는 `[]`를 명시해주었습니다. 이제 이것을 호출하고 결과를 봅시다.

저의 테스트 환경에서는 `0.092`초가 걸렸네요. 준수한 성능입니다.

# 결론

보시다시피, 다른 언어에 rust를 포함하는 것은 기본적으로 _매우_ 쉽습니다.
물론 여기서부터 해볼만한 더 많은 것들이 있지만요. 더 자세히 알아보고 싶다면
[FFI][ffi]챕터를 참고해주세요.