前回のイントロダクションにひき続いてLearn Rustの一部を翻訳してみました。
原文はhttps://doc.rust-lang.org/stable/book/README.html にあります。
原文のライセンスはこちら→https://github.com/rust-lang/rust/blob/master/COPYRIGHT
Learn Rust
ようこそ!このセクションでは、プロジェクトのビルドを通じてRustを教えるためのいくつかのチュートリアルがあります。あなたは高レベルな概要を取得できますが、詳細は読み飛ばすことになります。
もし、あなたがより多くの「根本的」スタイルの経験を得ることを好むなら、'Syntax and Sematics'を閲覧することを強くおすすめします。
Guessing Game
yohhoyさんが素敵な翻訳をされていますので、省略とさせていただきます。
[翻訳]Rustを学ぼう - 数字当てゲーム
Dining Philosophers
同じく、yohhoyさんが素敵な翻訳をされていますので、省略とさせていただきます。
[翻訳]Rustを学ぼう - 食事する哲学者
Rust Inside Other Languages
私達の三番目のプロジェクトとして、何かの言語を選んで、Rustの最大の強みを披露しましょう。 それはランタイムを必要としないということです。
組織の成長に従って、ますますプログラミング言語の多くに依存していきます。様々な言語はそれぞれ違った強味と弱味をもち、複数の言語をフルスタックで駆使すれば、効力を発揮する最も最適な言語を使用することができ、弱い部分には別の言語を使うことが出来ます。
多くのプログラミング言語は、実行時パフォーマンスに弱点を抱えています。多くの場合、プログラマのより大きな生産性と使用する言語の遅さはトレードオフ関係にあります。これを軽減するために、彼の言語たちはシステムの一部をC言語で書く方法と、より高レベルな言語で書かれたかのようにC言語のコードを呼び出す方法を提供します 。これは'foreign function interface'つまり「外部関数インターフェース 」と呼ばれ、しばしば、'FFI'と省略されます。
Rustは両方向のFFIをサポートしています。C言語のコードを簡単に呼び出せ、C言語からも簡単に呼び出すことができるということです。ガベージコレクタ-なしであることと、低レベルの実行要件を合わせることで、あなたがいくつか余分に”oomph”がほしいとき、Rustは多言語へ埋め込む際に最適な候補となります。1
全体とその詳細はchapter devoted to FFIとこの文書の他の場所に記載されており、この章では、Ruby、Python、JavaScriptの3つの例でFFIの特定のユースケースを調べてみます。
###The problem
選択することができるプロジェクトはいくつかありますが、ここではRustが他の言語に比べて明確に有利な点を持っている例を選択します:数値計算とスレッディングです。
多くの言語は、一貫性のために、スタック上よりもヒープ上の数値に配置します。特にオブジェクト指向プログラミングに特化し、ガベージコレクションを使用する言語では、ヒープ配置が普通です。時折行う最適化では特定の番号を割り当てスタックすることができますが、その仕事をオプティマイザーに任せるより、つねに、何らかのオブジェクト型を使うよりもプリミティブ数値型が使用されることを確実にしたい時があります。2
次に、多くの言語は「グローバルインタプリターロック3」をもち、これは多くの状況での同時実行を制御します。これは安全性の名のもとに行われ肯定的効果もありますが、同時に行うことのできる作業量を制限するという大きな負の側面ももたらします。
これらの2つの側面を強調するために、2つの側面を多分に含んだ小さなプロジェクトを作ることにします。例としてあげるものの焦点は問題自体より、言語に対するRustの埋め込みにあるので、特に意味のない例を使います:
Start ten threads. Inside each thread, count from one to five million. After All ten threads are finished, print out ‘done!’.
私は特定のコンピュータに基づき500万を選択しました。ここではRubyこのコードの例を書きます:
threads = []
10.times do
threads << Thread.new do
count = 0
5_000_000.times do
count += 1
end
end
end
threads.each {|t| t.join }
puts "done!"
この例を動かして、数秒間程度実行されるように番号を選択しなおしてください。コンピューターのハードウェアによって、数値を増減する必要があるかもしれません。4
私のシステムでは、このプロクラムを実行するのに2.156秒かかります。そして、top
のような何らかのプロセスモニタリングツールで使うと2、たったひとつのコアしか使用しないことが確認できます。これがGILなのです。
これは作り物のプログラムであることは事実ですが、同時に、現実の世界でも似たような多くの問題を想像できます。私達の目的は、手間のかかる高負荷な計算を、ある種の並列計算として表現しなおし、スレッドをビジー状態します。2
###A Rust library
この問題をRustで書きなおしましょう。最初は新しいプロジェクトをCargoで作ります:
$ 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_001) {
_x += 1
}
})
}).collect();
for h in handles {
h.join().ok().expect("Could not join a thread!");
}
}
これらの幾つかは過去の例ですでに知っていますね。10個のスレッドを回転させ、handles
ベクターにそれを回収し、各スレットの内部には、500万回のループがあり、毎回_X
に1を追加します。なぜアンダースコアをつけるのでしょう? たぶん, それを排除してコンパイルした場合:
$ cargo build
Compiling embed v0.1.0 (file:///home/steve/src/embed)
src/lib.rs:3:1: 16:2 warning: function is never used: `process`, #[warn(dead_code)] on by default
src/lib.rs:3 fn process() {
src/lib.rs:4 let handles: Vec<_> = (0..10).map(|_| {
src/lib.rs:5 thread::spawn(|| {
src/lib.rs:6 let mut x = 0;
src/lib.rs:7 for _ in (0..5_000_001) {
src/lib.rs:8 x += 1
...
src/lib.rs:6:17: 6:22 warning: variable `x` is assigned to, but never used, #[warn(unused_variables)] on by default
src/lib.rs:6 let mut x = 0;
^~~~~
その最初の警告は、ライブラリを構築するためのものです。もし、この関数のテストを持っていたら、この警告は消えてなくなるでしょう。しかし、今のところこれは決して呼び出されることはありません。5
次の警告は、x
と_x
に関連した警告です。それは、x
に対して実際に何かをすることはありませんので、それについての警告が表示されています。このケースでは、CPUサイクルを浪費しようとしているだけで、何かをしなくても問題無いです。x
の先頭にアンダースコアを つければ警告は消えます。
最後に、各スレッドに合流します。
しかしながら、いま、これはRustのライブラリーであり、C言語から呼び出せるよう公開していません。もしこれを他の言語から使おうとしても、それが機能することはありません6。けれども、たった2つの小さな変更を加えることでこの問題は解決します。最初にコードの先頭を変更します:
#[no_mangle]
pub extern fn process() {
no_mangle
という新しい属性を追加する必要があります。あなたがRustのライブラリーを作ったとき、コンパイル後の関数の名前を変更します。理由はこのチュートリアルの範囲外となりますが、他の言語が関数呼び出しの方法をわかるようにするためには、それをしない必要があります。この属性はその動作を無効にします。
その他の変更はpub extern
です。このpub
はこの関数が外部のモジュールから呼び出し可能でなければならないことを意味し、extern
はC言語から呼び出し可能であるべきであること意味しています。これでもう全体への変更の多くはおしまいです。
次に行うべきことは、Cargo.toml
の設定を変更することです。下部にこれを追加します:
[lib]
name = "embed"
crate-type = ["dylib"]
これはRustに標準動的ライブラリーにライブラリーをコンパイルしてほしいことを伝えます。デフォルトでは、RustはRust固有の形式である「rlib」にコンパイルします。
では、プロジェクトをビルドしてみましょう:
$ cargo build --release
Compiling embed v0.1.0 (file:///home/steve/src/embed)
cargo build --release
を選択し、ビルド時の最適化を有効化しました。これで可能な限り速くするのです!あなたはtarget/release階層に出来上がったライブラリを見つけることが出来ます:
$ ls target/release/
build deps examples libembed.so native
このlibembed.so
は「shared object」ライブラリです。ちょうどC言語で書かれた共有ライブラリのようにこれを使うことが出来ます!余談として、プラットフォームによってこれはembed.dll
だったりlibembed.dylib
だったりするかもしれません。
では、ビルドされたRustライブラリーが手元にあるので、Rubyから使ってみましょう。
###Ruby
プロジェクトの内部にembed.rb
を開き、これを行います:
require 'ffi'
module Hello
extend FFI::Library
ffi_lib 'target/release/libembed.so'
attach_function :process, [], :void
end
Hello.process
puts "done!”
これを実行する前にgemからffi
をインストールしておきましょう:
$ 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
それが終わったら、実際に実行してみましょう:
$ ruby embed.rb
done!
$
Whoah, that was fast! 私のシステムでは、0.086秒しかかからず、すべてRubyで記述されたバージョンはむしろ2秒以上かかりました。このRubyのコードを詳しく見て行きましょう:
require 'ffi'
まず最初にffi
のgemを要求する必要があります。これを、CライブラリーのようなRustライブラリとのインターフェースにすることが出来ます。
module Hello
extend FFI::Library
ffi_lib 'target/release/libembed.so'
このffi
gemの作者は、共有ライブラリからインポートする関数の範囲にモジュールを使用することを推奨しています。内部では、必要なFFI::Library
を拡張し、その後、共通オブジェクトライブラリをロードするためにffi_lib
を呼び出します。ここに前に見たような、ライブラリが格納されているパスであるtarget/release/libembed.so
を渡します。
attach_function :process, [], :void
このattach_function
関数はFFI gemによって提供されます。それは同じ名前のRubyの関数に、Rustのprocess()
関数を接続します。process()
は引数を取らないので、次のパラメーターは空の配列になり、何も返すこともないので、最後の引数として:void
を渡しています。
Hello.process
これはRustに対しての実際の呼び出しです。モジュールの組み合わせとattach_function
への呼び出しは全てこのようになります。Rubyの関数のようですが、まさしくRustなのです!
puts "done!"
最後に、プロジェクトの要件に従って、done!
を出力します。
That’s it! ここまで見てきたように、2つの言語間の橋渡しはほんとうに簡単に、より高いパフォーマンスを手に入れることができます。
次は、Pythonに挑戦です!
###Python
このディレクトリにembed.py
を作り、下の文を書きます:
from ctypes import cdll
lib = cdll.LoadLibrary("target/release/libembed.so")
lib.process()
print("done!")
Even easier! ctypes
モジュールからcdll
を使用し、LoadLibrary
を迅速に呼び出した後、process()
を呼び出すことが出来ます。
私のシステムでは、0.017秒かかりました。Speedy!
###Node.js
Nodeは言語ではありませんが、現在、サーバーサイドJavaScriptにおける支配的な実装です。
NodeでFFIを行うために、最初にライブラリーをイントールする必要があります:
$ npm install ffi
インストール後は、下のことができるようになります:
var ffi = require('ffi');
var lib = ffi.Library('target/release/libembed', {
'process': [ 'void', [] ]
});
lib.process();
console.log("done!");
Pythonの例よりRubyの例に近いことが分かるかと思います。ffi
モジュールを使ってffi.Library()
にアクセスして、共有オブジェクトをロードします。関数の戻り値と引数には注釈が必要であり、戻り値のために「void」を、引数を意味しないようにからの配列をそれぞれ注釈します。そこから、それを呼び出し結果を出力します。
私のシステムでは、0.090秒かかります。
###Conclusion
ご覧にいただけるように、これらを行う基本は非常に簡単です。もちろん、ここで行えることはもっとたくさんあります。詳細はFFIの章をご覧ください。
##感想
やっぱり日本語と感覚が違うせいで厳密に意味を捉えることが難しい部分も多々ありました。特に感嘆詞なんか日本語に直訳するとすごくダサくなってしまいます。もちろん勉強不足な部分もあるにはありますが。
とはいえ、前回よりは訳しやすかった気がします。
その他、ご意見、ご感想ございましたらコメント、編集リクエストなどよろしくお願いします。
##訳注一覧