注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
このガイドでは、他言語コードのためのバインディングを書く導入に snappy という圧縮・展開ライブラリを使います。
Rustは現在、C++ライブラリを直接呼び出すことができませんが、snappyはCのインターフェイスを持っています(ドキュメントが snappy-c.h
にあります)。
これらの例の多くは libc
クレート を使っています。これは、主にCの様々な型の定義を提供するものです。
もしこれらの例を自分で試すのであれば、次のように libc
を Cargo.toml
に追加する必要があるでしょう。
[dependencies]
libc = "0.2.0"
そして、クレートのルートに extern crate libc;
を追加しましょう。
次のコードは、snappyがインストールされていればコンパイルできる他言語関数を呼び出す最小の例です。
#![feature(libc)] extern crate libc; use libc::size_t; #[link(name = "snappy")] extern { fn snappy_max_compressed_length(source_length: size_t) -> size_t; } fn main() { let x = unsafe { snappy_max_compressed_length(100) }; println!("max compressed length of a 100 byte buffer: {}", x); }extern crate libc; use libc::size_t; #[link(name = "snappy")] extern { fn snappy_max_compressed_length(source_length: size_t) -> size_t; } fn main() { let x = unsafe { snappy_max_compressed_length(100) }; println!("max compressed length of a 100 byte buffer: {}", x); }
extern
ブロックは他言語ライブラリの中の関数のシグネチャ、この例ではそのプラットフォーム上のC ABIによるもののリストです。
#[link(...)]
アトリビュートは、シンボルが解決できるように、リンカに対してsnappyのライブラリをリンクするよう指示するために使われています。
他言語関数はアンセーフとみなされるので、それらを呼び出すには、この中に含まれているすべてのものが本当に安全であるということをコンパイラに対して約束するために、 unsafe {}
で囲まなければなりません。
Cライブラリは、スレッドセーフでないインターフェイスを公開していることがありますし、ポインタを引数に取る関数のほとんどは、ポインタがダングリングポインタになる可能性を有しているので、すべての入力に対して有効なわけではありません。そして、生ポインタはRustの安全なメモリモデルから外れてしまいます。
他言語関数について引数の型を宣言するとき、Rustのコンパイラはその宣言が正しいかどうかを確認することができません。それを正しく指定することは実行時にバインディングを正しく動作させるために必要なことです。
extern
ブロックはsnappyのAPI全体をカバーするように拡張することができます。
extern crate libc; use libc::{c_int, size_t}; #[link(name = "snappy")] extern { fn snappy_compress(input: *const u8, input_length: size_t, compressed: *mut u8, compressed_length: *mut size_t) -> c_int; fn snappy_uncompress(compressed: *const u8, compressed_length: size_t, uncompressed: *mut u8, uncompressed_length: *mut size_t) -> c_int; fn snappy_max_compressed_length(source_length: size_t) -> size_t; fn snappy_uncompressed_length(compressed: *const u8, compressed_length: size_t, result: *mut size_t) -> c_int; fn snappy_validate_compressed_buffer(compressed: *const u8, compressed_length: size_t) -> c_int; }
生のC APIは、メモリの安全性を提供し、ベクタのようなもっと高レベルの概念を使うようにラップしなければなりません。 ライブラリは安全で高レベルなインターフェイスのみを公開するように選択し、アンセーフな内部の詳細を隠すことができます。
バッファを期待する関数をラップするには、Rustのベクタをメモリへのポインタとして操作するために slice::raw
モジュールを使います。
Rustのベクタは隣接したメモリのブロックであることが保証されています。
その長さは現在含んでいる要素の数で、容量は割り当てられたメモリの要素の合計のサイズです。
長さは、容量以下です。
pub fn validate_compressed_buffer(src: &[u8]) -> bool { unsafe { snappy_validate_compressed_buffer(src.as_ptr(), src.len() as size_t) == 0 } }
上の validate_compressed_buffer
ラッパは unsafe
ブロックを使っていますが、関数のシグネチャを unsafe
から外すことによって、その呼出しがすべての入力に対して安全であることが保証されています。
結果を保持するようにバッファを割り当てなければならないため、 snappy_compress
関数と snappy_uncompress
関数はもっと複雑です。
snappy_max_compressed_length
関数は、圧縮後の結果を保持するために必要な最大の容量のベクタを割り当てるために使うことができます。
そして、そのベクタは結果を受け取るための引数としてsnappy_compress
関数に渡されます。
結果を受け取るための引数は、長さをセットするために、圧縮後の本当の長さを取得するためにも渡されます。
pub fn compress(src: &[u8]) -> Vec<u8> { unsafe { let srclen = src.len() as size_t; let psrc = src.as_ptr(); let mut dstlen = snappy_max_compressed_length(srclen); let mut dst = Vec::with_capacity(dstlen as usize); let pdst = dst.as_mut_ptr(); snappy_compress(psrc, srclen, pdst, &mut dstlen); dst.set_len(dstlen as usize); dst } }
snappyは展開後のサイズを圧縮フォーマットの一部として保存していて、 snappy_uncompressed_length
が必要となるバッファの正確なサイズを取得するため、展開も同様です。
pub fn uncompress(src: &[u8]) -> Option<Vec<u8>> { unsafe { let srclen = src.len() as size_t; let psrc = src.as_ptr(); let mut dstlen: size_t = 0; snappy_uncompressed_length(psrc, srclen, &mut dstlen); let mut dst = Vec::with_capacity(dstlen as usize); let pdst = dst.as_mut_ptr(); if snappy_uncompress(psrc, srclen, pdst, &mut dstlen) == 0 { dst.set_len(dstlen as usize); Some(dst) } else { None // SNAPPY_INVALID_INPUT } } }
参考のために、ここで使った例は GitHub上のライブラリ としても置いておきます。
他言語ライブラリはリソースの所有権を呼出先のコードに手渡してしまうことがあります。 そういうことが起きる場合には、安全性を提供し、それらのリソースが解放されることを保証するために、Rustのデストラクタを使わなければなりません(特にパニックした場合)。
デストラクタについて詳しくは、Dropトレイトを見てください。
外部のライブラリの中には、現在の状況や中間的なデータを呼出元に報告するためにコールバックを使わなければならないものがあります。
Rustで定義された関数を外部のライブラリに渡すことは可能です。
これをするために必要なのは、Cのコードから呼び出すことができるように正しい呼出規則に従って、コールバック関数を extern
としてマークしておくことです。
そして、登録呼出しを通じてコールバック関数をCのライブラリに送ることができるようになり、後でそれらから呼び出すことができるようになります。
基本的な例は次のとおりです。
これがRustのコードです。
extern fn callback(a: i32) { println!("I'm called from C with value {0}", a); } #[link(name = "extlib")] extern { fn register_callback(cb: extern fn(i32)) -> i32; fn trigger_callback(); } fn main() { unsafe { register_callback(callback); // trigger_callback(); // Triggers the callback trigger_callback(); // コールバックをトリガする } }extern fn callback(a: i32) { println!("I'm called from C with value {0}", a); } #[link(name = "extlib")] extern { fn register_callback(cb: extern fn(i32)) -> i32; fn trigger_callback(); } fn main() { unsafe { register_callback(callback); trigger_callback(); // コールバックをトリガする } }
これがCのコードです。
typedef void (*rust_callback)(int32_t);
rust_callback cb;
int32_t register_callback(rust_callback callback) {
cb = callback;
return 1;
}
void trigger_callback() {
cb(7); // Rustのcallback(7)を呼び出す
}
この例では、Rustの main()
がCの trigger_callback()
を呼び出し、今度はそれが、Rustの callback()
をコールバックしています。
先程の例では、グローバルな関数をCのコードから呼ぶための方法を示してきました。 しかし、特別なRustのオブジェクトをコールバックの対象にしたいことがあります。 これは、そのオブジェクトをそれぞれCのオブジェクトのラッパとして表現することで可能になります。
これは、そのオブジェクトへの生ポインタをCライブラリに渡すことで実現できます。 そして、CのライブラリはRustのオブジェクトへのポインタをその通知の中に含むことができるようになります。 これにより、そのコールバックは参照されるRustのオブジェクトにアンセーフな形でアクセスできるようになります。
これがRustのコードです。
#[repr(C)] struct RustObject { a: i32, // other members // その他のメンバ } extern "C" fn callback(target: *mut RustObject, a: i32) { println!("I'm called from C with value {0}", a); unsafe { // Update the value in RustObject with the value received from the callback // コールバックから受け取った値でRustObjectの中の値をアップデートする (*target).a = a; } } #[link(name = "extlib")] extern { fn register_callback(target: *mut RustObject, cb: extern fn(*mut RustObject, i32)) -> i32; fn trigger_callback(); } fn main() { // Create the object that will be referenced in the callback // コールバックから参照されるオブジェクトを作成する let mut rust_object = Box::new(RustObject { a: 5 }); unsafe { register_callback(&mut *rust_object, callback); trigger_callback(); } }#[repr(C)] struct RustObject { a: i32, // その他のメンバ } extern "C" fn callback(target: *mut RustObject, a: i32) { println!("I'm called from C with value {0}", a); unsafe { // コールバックから受け取った値でRustObjectの中の値をアップデートする (*target).a = a; } } #[link(name = "extlib")] extern { fn register_callback(target: *mut RustObject, cb: extern fn(*mut RustObject, i32)) -> i32; fn trigger_callback(); } fn main() { // コールバックから参照されるオブジェクトを作成する let mut rust_object = Box::new(RustObject { a: 5 }); unsafe { register_callback(&mut *rust_object, callback); trigger_callback(); } }
これがCのコードです。
typedef void (*rust_callback)(void*, int32_t);
void* cb_target;
rust_callback cb;
int32_t register_callback(void* callback_target, rust_callback callback) {
cb_target = callback_target;
cb = callback;
return 1;
}
void trigger_callback() {
cb(cb_target, 7); // Rustのcallback(&rustObject, 7)を呼び出す
}
先程の例では、コールバックは外部のCライブラリへの関数呼出しに対する直接の反応として呼びだされました。 実行中のスレッドの制御はコールバックの実行のためにRustからCへ、そしてRustへと切り替わりますが、最後には、コールバックはコールバックを引き起こした関数を呼び出したものと同じスレッドで実行されます。
外部のライブラリが独自のスレッドを生成し、そこからコールバックを呼び出すときには、事態はもっと複雑になります。
そのような場合、コールバックの中のRustのデータ構造へのアクセスは特にアンセーフであり、適切な同期メカニズムを使わなければなりません。
ミューテックスのような古典的な同期メカニズムの他にも、Rustではコールバックを呼び出したCのスレッドからRustのスレッドにデータを転送するために( std::sync::mpsc
の中の)チャネルを使うという手もあります。
もし、非同期のコールバックがRustのアドレス空間の中の特別なオブジェクトを対象としていれば、それぞれのRustのオブジェクトが破壊された後、Cのライブラリからそれ以上コールバックが実行されないようにすることが絶対に必要です。 これは、オブジェクトのデストラクタでコールバックの登録を解除し、登録解除後にコールバックが実行されないようにライブラリを設計することで実現できます。
extern
ブロックの中の link
アトリビュートは、rustcに対してネイティブライブラリをどのようにリンクするかを指示するための基本的な構成ブロックです。
今のところ、2つの形式のlinkアトリビュートが認められています。
#[link(name = "foo")]
#[link(name = "foo", kind = "bar")]
これらのどちらの形式でも、 foo
はリンクするネイティブライブラリの名前で、2つ目の形式の bar
はコンパイラがリンクするネイティブライブラリの種類です。
3つのネイティブライブラリの種類が知られています。
#[link(name = "readline")]
#[link(name = "my_build_dependency", kind = "static")]
#[link(name = "CoreFoundation", kind = "framework")]
フレームワークはOSXターゲットでのみ利用可能であることに注意しましょう。
異なる kind
の値はリンク時のネイティブライブラリの使われ方の違いを意味します。
リンクの視点からすると、Rustコンパイラは2種類の生成物を作ります。
部分生成物(rlib/staticlib)と最終生成物(dylib/binary)です。
ネイティブダイナミックライブラリとフレームワークの依存関係は最終生成物を作るときまで伝播され解決されますが、スタティックライブラリの依存関係は全く伝えません。なぜなら、スタティックライブラリはその後に続く生成物に直接統合されてしまうからです。
このモデルをどのように使うことができるのかという例は次のとおりです。
libfoo.a
にアーカイブされ、それからRustのクレートで #[link(name = "foo", kind = "static")]
によって依存関係を宣言します。クレートの成果物の種類にかかわらず、ネイティブスタティックライブラリは成果物に含まれます。これは、ネイティブスタティックライブラリの配布は不要だということを意味します。
readline
のような)一般的なシステムライブラリは多くのシステムで利用可能となっていますが、それらのライブラリのスタティックなコピーは見付からないことがしばしばあります。
この依存関係をRustのクレートに含めるときには、(rlibのような)部分生成物のターゲットはライブラリをリンクしませんが、(binaryのような)最終生成物にrlibを含めるときには、ネイティブライブラリはリンクされます。OSXでは、フレームワークはダイナミックライブラリと同じ意味で振る舞います。
生ポインタの参照外しやアンセーフであるとマークされた関数の呼出しなど、いくつかの作業はアンセーフブロックの中でのみ許されます。 アンセーフブロックはアンセーフ性を隔離し、コンパイラに対してアンセーフ性がブロックの外に漏れ出さないことを約束します。
一方、アンセーフな関数はそれを全世界に向けて広告します。 アンセーフな関数はこのように書きます。
fn main() { unsafe fn kaboom(ptr: *const i32) -> i32 { *ptr } }unsafe fn kaboom(ptr: *const i32) -> i32 { *ptr }
この関数は unsafe
ブロック又は他の unsafe
な関数からのみ呼び出すことができます。
他言語APIはしばしばグローバルな状態を追跡するようなことをするためのグローバル変数をエクスポートします。
それらの値にアクセスするために、それらを extern
ブロックの中で static
キーワードを付けて宣言します。
extern crate libc; #[link(name = "readline")] extern { static rl_readline_version: libc::c_int; } fn main() { println!("You have readline version {} installed.", rl_readline_version as i32); }
あるいは、他言語インターフェイスが提供するグローバルな状態を変更しなければならないこともあるかもしれません。
これをするために、スタティックな値を変更することができるように mut
付きで宣言することができます。
extern crate libc; use std::ffi::CString; use std::ptr; #[link(name = "readline")] extern { static mut rl_prompt: *const libc::c_char; } fn main() { let prompt = CString::new("[my-awesome-shell] $").unwrap(); unsafe { rl_prompt = prompt.as_ptr(); println!("{:?}", rl_prompt); rl_prompt = ptr::null(); } }
static mut
の付いた作用は全て、読込みと書込みの双方についてアンセーフであることに注意しましょう。
グローバルでミュータブルな状態の扱いには多大な注意が必要です。
他言語で書かれたコードの多くはC ABIをエクスポートしていて、Rustは他言語関数の呼出しのときのデフォルトとしてそのプラットフォーム上のCの呼出規則を使います。 他言語関数の中には、特にWindows APIですが、他の呼出規則を使うものもあります。 Rustにはコンパイラに対してどの規則を使うかを教える方法があります。
#![feature(libc)] extern crate libc; #[cfg(all(target_os = "win32", target_arch = "x86"))] #[link(name = "kernel32")] #[allow(non_snake_case)] extern "stdcall" { fn SetEnvironmentVariableA(n: *const u8, v: *const u8) -> libc::c_int; } fn main() { }extern crate libc; #[cfg(all(target_os = "win32", target_arch = "x86"))] #[link(name = "kernel32")] #[allow(non_snake_case)] extern "stdcall" { fn SetEnvironmentVariableA(n: *const u8, v: *const u8) -> libc::c_int; }
これは extern
ブロック全体に適用されます。
サポートされているABIの規則は次のとおりです。
stdcall
aapcs
cdecl
fastcall
Rust
rust-intrinsic
system
C
win64
このリストのABIのほとんどは名前のとおりですが、 system
ABIは少し変わっています。
この規則はターゲットのライブラリを相互利用するために適切なABIを選択します。
例えば、x86アーキテクチャのWin32では、使われるABIは stdcall
になります。
しかし、x86_64では、Windowsは C
の呼出規則を使うので、 C
が使われます。
先程の例で言えば、 extern "system" { ... }
を使って、x86のためだけではなく全てのWindowsシステムのためのブロックを定義することができるということです。
#[repr(C)]
アトリビュートが適用されている場合に限り、Rustは struct
のレイアウトとそのプラットフォーム上のCでの表現方法との互換性を保証します。
#[repr(C, packed)]
を使えば、パディングなしで構造体のメンバをレイアウトすることができます。
#[repr(C)]
は列挙型にも適用することができます。
Rust独自のボックス( Box<T>
)は包んでいるオブジェクトを指すハンドルとして非ヌルポインタを使います。
しかし、それらは内部のアロケータによって管理されるため、手で作るべきではありません。
参照は型を直接指す非ヌルポインタとみなすことが安全にできます。
しかし、借用チェックやミュータブルについてのルールが破られた場合、安全性は保証されません。生ポインタについてはコンパイラは借用チェックやミュータブルほどには仮定を置かないので、必要なときには、生ポインタ( *
)を使いましょう。
ベクタと文字列は基本的なメモリレイアウトを共有していて、 vec
モジュールと str
モジュールの中のユーティリティはC APIで扱うために使うことができます。
ただし、文字列は \0
で終わりません。
Cと相互利用するためにNUL終端の文字列が必要であれば、 std::ffi
モジュールの CString
型を使う必要があります。
crates.ioのlibc
クレート は libc
モジュール内にCの標準ライブラリの型の別名や関数の定義を含んでいて、Rustは libc
と libm
をデフォルトでリンクします。
いくつかの型は非 null
であると定義されています。
このようなものには、参照( &T
、 &mut T
)、ボックス( Box<T>
)、そして関数ポインタ( extern "abi" fn()
)があります。
Cとのインターフェイスにおいては、ヌルになり得るポインタが使われることがしばしばあります。
特別な場合として、ジェネリックな enum
がちょうど2つのバリアントを持ち、そのうちの1つが値を持っていなくてもう1つが単一のフィールドを持っているとき、それは「ヌルになり得るポインタの最適化」の対象になります。
そのような列挙型が非ヌルの型でインスタンス化されたとき、それは単一のポインタとして表現され、データを持っていない方のバリアントはヌルポインタとして表現されます。
Option<extern "C" fn(c_int) -> c_int>
は、C ABIで使われるヌルになり得る関数ポインタの表現方法の1つです。
RustのコードをCから呼び出せる方法でコンパイルしたいときがあるかもしれません。 これは割と簡単ですが、いくつか必要なことがあります。
#[no_mangle] pub extern fn hello_rust() -> *const u8 { "Hello, world!\0".as_ptr() } fn main() {}#[no_mangle] pub extern fn hello_rust() -> *const u8 { "Hello, world!\0".as_ptr() }
extern
は先程「 他言語呼出規則 」で議論したように、この関数をCの呼出規則に従うようにします。
no_mangle
アトリビュートはRustによる名前のマングリングをオフにして、リンクしやすいようにします。
FFIを扱うときに panic!
に注意することは重要です。
FFIの境界をまたぐ panic!
の動作は未定義です。
もしあなたがパニックし得るコードを書いているのであれば、他のスレッドで実行して、パニックがCに波及しないようにすべきです。
use std::thread; #[no_mangle] pub extern fn oh_no() -> i32 { let h = thread::spawn(|| { panic!("Oops!"); }); match h.join() { Ok(_) => 1, Err(_) => 0, } }
ときどき、Cのライブラリが何かのポインタを要求してくるにもかかわらず、その要求されているものの内部的な詳細を教えてくれないことがあります。
最も単純な方法は void *
引数を使うことです。
void foo(void *arg);
void bar(void *arg);
Rustではこれを c_void
型で表現することができます。
extern crate libc; extern "C" { pub fn foo(arg: *mut libc::c_void); pub fn bar(arg: *mut libc::c_void); }
これはその状況に対処するための完全に正当な方法です。
しかし、もっとよい方法があります。
これを解決するために、いくつかのCライブラリでは、代わりに struct
を作っています。そこでは構造体の詳細とメモリレイアウトはプライベートです。
これは型の安全性をいくらか満たします。
それらの構造体は「オペーク」と呼ばれます。
これがCによる例です。
struct Foo; /* Fooは構造体だが、その内容は公開インターフェイスの一部ではない */
struct Bar;
void foo(struct Foo *arg);
void bar(struct Bar *arg);
これをRustで実現するために、enum
で独自のオペーク型を作りましょう。
pub enum Foo {} pub enum Bar {} extern "C" { pub fn foo(arg: *mut Foo); pub fn bar(arg: *mut Bar); }
バリアントなしの enum
を使って、バリアントがないためにインスタンス化できないオペーク型を作ります。
しかし、 Foo
型と Bar
型は異なる型であり、2つものの間の型の安全性を満たすので、 Foo
のポインタを間違って bar()
に渡すことはなくなります。