Rust のライブラリ crate_type をまとめてみた
2022/01/13 追記
最初の投稿から 5 年も経ったらさすがに他の記事出てるだろうと思いきや、今でも Google で検索するといちばん上に出てきますね。
ということで改めて調べる必要があったことに加え、内容に不正確な点があったので、ほぼ全面的に書き直しました。過去の版は 編集履歴 から確認してください。
はじめに
2016/07/07 に Rust 1.10 が発表されました。もう 5 年前です。
Announcing Rust 1.10 – The Rust Programming Language Blog
Rust 1.10 では、新しく cdylib というが追加されました。
これは一体何者なのでしょうか。そもそも Rust の crate_type とは何でしょうか。
TL;DR
-
- クレートの種類は bin, lib, proc-macro の 3 つ
-
- lib クレートは次章に記載の通り、生成するファイルを *lib の形で指定することができる。
-
- Rust を利用する人が指定する可能性があるのは基本的に rlib と cdylib の 2 つ。
rlib は Cargo の中で使用するために指定する(されている)。
cdylib は他の言語から動的に FFI するライブラリを作るために指定する。
答え
疑問に対する回答は Linkage – The Rust Reference にあります。違いについて要約すると、以下のことが書いてあります。
bin : 実行可能なファイルを生成します。
lib : Rust ライブラリを生成します。詳細はコンパイラに依存します。
dylib : 動的な Rust ライブラリを生成します。
staticlib : 静的な システム ライブラリを生成します。他言語アプリからリンクするために推奨されます。
cdylib : 動的な システム ライブラリを生成します。他言語からロードするために使用されます。
rlib : “Rust library” を生成します。静的な Rust ライブラリとも言え、中間生成物として使用されます。
proc-macro : 何を生成するかは特定していせん。関数マクロが定義されたクレートに設定します。
ここでは他にも解説がある bin と proc-macro については触れず、ライブラリに限定します。
内容が指定されていない lib を除くと、ライブラリの種類は下表の通りに整理できます。
dylib
cdylib
静的(リンク)rlib
staticlib
ここでの「システムライブラリ」とは、他の言語から FFI などを使用して呼び出すことを想定したものです。
つまり、裏を返せば「Rust ライブラリ」とは、他の言語から使用されることを明確には想定していない形式とも言えます。
実際に rlib については 中間生成物 と書かれている通り、無指定でも Cargo の内部で使用されています。
外部パッケージを使っているクレートをビルドすると、rlib ファイルが ./target//deps の下へ大量に生成される様子が観察できます。
実験
とりあえず分かった気になれたので、ここで少し実験してみましょう。
環境は rustc 1.57.0 (f1edd0429 2021-11-29) on Windows 10 です。
サンプルとして以下のコードを使用します。
#[no_mangle]
pub fn saturating_add(a: u8, b: u8) -> u8 {
a.saturating_add(b)
}
Rust 標準の u8::saturating_add をラップしただけの関数をエクスポートしています。
後ほど外部から呼び出せるように no_mangle 属性を指定しています。
これを以下の通りにコンパイルしてみます。なお、余計な情報が入らないよう最適化しています。
また歴史的な都合から dylib には prefer-dynamic というフラグがあるので、これを付けたものを追加します。
rustc add.rs -O --crate-type=cdylib --out-dir cdylib
rustc add.rs -O --crate-type=dylib --out-dir dylib
rustc add.rs -O --crate-type=dylib --out-dir dylib-dyn -C prefer-dynamic
rustc add.rs -O --crate-type=rlib --out-dir rlib
rustc add.rs -O --crate-type=staticlib --out-dir staticlib
ファイルの中身を確認する
生成された複数のファイルから、メインとなるものを抜粋すると以下の通りです。
cdylib を覗いてみる
まずは cdylib を覗いてみましょう。
ファイル形式は拡張子に .dll とある通り Portable Executable なので、出力シンボルと依存先ライブラリを見てみます。
R:\crate_type> dumpbin /nologo /exports /dependents .\cdylib\add.dll
Dump of file .\cdylib\add.dll
File Type: DLL
Image has the following dependencies:
KERNEL32.dll
VCRUNTIME140.dll
api-ms-win-crt-runtime-l1-1-0.dll
Section contains the following exports for add.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
2 number of functions
2 number of names
ordinal hint RVA name
1 0 00001360 rust_eh_personality = rust_eh_personality
2 1 00001000 saturating_add = saturating_add
Summary
1000 .data
1000 .pdata
1000 .rdata
1000 .reloc
2000 .text
さきほど実装した saturating_add と、no_std の Rust を書いたことがある人にはお馴染みの rust_eh_personality が見て取れます。それ以外に特筆する情報はなさそうです。
dylib を覗いてみる
続いて dylib の dll を見てみます。
R:\crate_type> dumpbin /nologo /exports /dependents .\dylib\add.dll
Dump of file .\dylib\add.dll
File Type: DLL
Image has the following dependencies:
KERNEL32.dll
WS2_32.dll
bcrypt.dll
ADVAPI32.dll
USERENV.dll
VCRUNTIME140.dll
api-ms-win-crt-math-l1-1-0.dll
api-ms-win-crt-string-l1-1-0.dll
api-ms-win-crt-runtime-l1-1-0.dll
api-ms-win-crt-heap-l1-1-0.dll
Section contains the following exports for add.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
2436 number of functions
2436 number of names
ordinal hint RVA name
1 0 0004ED70 _ZN100_$LT$core..ascii..EscapeDefault$u20$as$u20$core..iter..traits..double_ended..DoubleEndedIterator$GT$9next_back17h1498a44032c8561eE = _ZN100_$LT$core..ascii..EscapeDefault$u20$as$u20$core..iter..traits..double_ended..DoubleEndedIterator$GT$9next_back17h1498a44032c8561eE
2 1 000104F0 _ZN101_$LT$std..fs..DirEntry$u20$as$u20$std..sys_common..AsInner$LT$std..sys..windows..fs..DirEntry$GT$$GT$8as_inner17h81e9e878d30be855E = _ZN101_$LT$std..fs..DirEntry$u20$as$u20$std..sys_common..AsInner$LT$std..sys..windows..fs..DirEntry$GT$$GT$8as_inner17h81e9e878d30be855E
3 2 000104F0 _ZN101_$LT$std..fs..FileType$u20$as$u20$std..sys_common..AsInner$LT$std..sys..windows..fs..FileType$GT$$GT$8as_inner17he61019d1f94252c1E = _ZN101_$LT$std..fs..DirEntry$u20$as$u20$std..sys_common..AsInner$LT$std..sys..windows..fs..DirEntry$GT$$GT$8as_inner17h81e9e878d30be855E
4 3 000104F0 _ZN101_$LT$std..fs..Metadata$u20$as$u20$std..sys_common..AsInner$LT$std..sys..windows..fs..FileAttr$GT$$GT$8as_inner17h9e589444f15fa30dE = _ZN101_$LT$std..fs..DirEntry$u20$as$u20$std..sys_common..AsInner$LT$std..sys..windows..fs..DirEntry$GT$$GT$8as_inner17h81e9e878d30be855E
5 4 00019DA0 _ZN101_$LT$std..process..ExitStatusError$u20$as$u20$core..convert..Into$LT$std..process..ExitStatus$GT$$GT$4into17h6a80afb100a680cbE = _ZN101_$LT$std..process..ExitStatusError$u20$as$u20$core..convert..Into$LT$std..process..ExitStatus$GT$$GT$4into17h6a80afb100a680cbE
6 5 0001C440 _ZN102_$LT$std..net..addr..SocketAddr$u20$as$u20$core..convert..From$LT$std..net..addr..SocketAddrV4$GT$$GT$4from17h35e23e4d3107c779E = _ZN102_$LT$std..net..addr..SocketAddr$u20$as$u20$core..convert..From$LT$std..net..addr..SocketAddrV4$GT$$GT$4from17h35e23e4d3107c779E
7 6 0001C460 _ZN102_$LT$std..net..addr..SocketAddr$u20$as$u20$core..convert..From$LT$std..net..addr..SocketAddrV6$GT$$GT$4from17h31922aeaac04cd61E = _ZN102_$LT$std..net..addr..SocketAddr$u20$as$u20$core..convert..From$LT$std..net..addr..SocketAddrV6$GT$$GT$4from17h31922aeaac04cd61E
8 7 0000FEE0 _ZN103_$LT$core..array..TryFromSliceError$u20$as$u20$core..convert..From$LT$core..convert..Infallible$GT$$GT$4from17h871ca03c104fd2bcE = _ZN103_$LT$core..array..TryFromSliceError$u20$as$u20$core..convert..From$LT$core..convert..Infallible$GT$$GT$4from17h871ca03c104fd2bcE
~~~~~~~~ 省略 ~~~~~~~~
2429 97C 00076030 __unorddf2 = _ZN17compiler_builtins5float3cmp10__unorddf217h4f257f44582e6d90E
2430 97D 00075F40 __unordsf2 = _ZN17compiler_builtins5float3cmp10__unordsf217h3bb3ef0e2ab6043aE
2431 97E 0002D0C0 rust_begin_unwind = rust_begin_unwind
2432 97F 0003E3A0 rust_eh_personality = _ZN58_$LT$libc..windows..FILE$u20$as$u20$core..clone..Clone$GT$5clone17hca7d4b7358131f48E
2433 980 000E2000 rust_metadata_add_99d1d8310a660620 = rust_metadata_add_99d1d8310a660620
2434 981 0002BDD0 rust_oom = rust_oom
2435 982 0002D990 rust_panic = rust_panic
2436 983 00001000 saturating_add = saturating_add
Summary
1000 .data
6000 .pdata
5F000 .rdata
1000 .reloc
1000 .rustc
7B000 .text
はい、シンボル名は思いきり省略しました。冒頭に 2436 number of functions とある通り、2436 個の関数が表示されます。さきほど実装した saturating_add は、そのうち 2436 番目となっていました。
増えた関数の name には見慣れた名前があります。これら cdylib にはなかった 2400 あまりの関数は、Rust の標準ライブラリを再出力したものであることが見て取れます。
それから依存先のほうにも、さきほどは存在しなかった dll が見て取れます。例えば WS2_32.dll は Windows Sockets 2 (Winsock) なので、ネットワークに関係しています。
これについてもコード上からは使用していない Rust の標準ライブラリを再出力しているため、このような結果となります。
それともう一つ、Summary のところに .rustc という領域があります。ここには型定義を含めたコンパイラのメタ情報が入っています。後ほど確認します。
dylib + prefer-dynamic を覗いてみる
次は dylib に -C prefer-dynamic フラグをつけたものです。
R:\crate_type> dumpbin /nologo /exports /dependents .\dylib-dyn\add.dll
Dump of file .\dylib-dyn\add.dll
File Type: DLL
Image has the following dependencies:
VCRUNTIME140.dll
api-ms-win-crt-runtime-l1-1-0.dll
KERNEL32.dll
std-f87c887dcbebcf7e.dll
Section contains the following exports for add.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
6 number of functions
6 number of names
ordinal hint RVA name
1 0 00001B92 __rust_alloc = __rust_alloc
2 1 00001B98 __rust_alloc_zeroed = __rust_alloc_zeroed
3 2 00001B9E __rust_dealloc = __rust_dealloc
4 3 00001BA4 __rust_realloc = __rust_realloc
5 4 00005000 rust_metadata_add_99d1d8310a660620 = rust_metadata_add_99d1d8310a660620
6 5 00001000 saturating_add = saturating_add
Summary
1000 .data
1000 .pdata
1000 .rdata
1000 .reloc
1000 .rustc
1000 .text
標準ライブラリが見つからないどころか、今度は依存先として指定されています。
rlib を覗いてみる
続いて rlib を見てみましょう。
こちらは拡張子が .rlib なので、まずはファイル形式を特定してみましょう。テキストエディタで開くと以下の記述で始まります。
!<arch>
/ 0 0 0 0 24 `
~~~~~~~~ 以下、省略 ~~~~~~~~
ということで(?)ファイル形式は Ar アーカイブです。
展開するとオブジェクトファイルやらメタデータやらが入っていますが、このままでは何だか分かりません。rustc 専用のファイルといったところです。
……staticlib は?
staticlib は dylib と OS のライブラリを静的リンクしたようなもので、貼りつけるには大きすぎるので省略します。
Rust から呼んでみる
せっかくライブラリを作ったので、呼び出してみましょう。以下のコードを使用します。
当然 $200 + 200 = 400$ ですが、saturating されれば u8::MAX の $255$ になるはずです。
extern crate add;
fn main() {
println!("{}", add::saturating_add(200, 200));
}
Rust から rlib を呼んでみる
まずはいつも (Cargo で) 使っている rlib から。
rustc の -L オプションによって、リンカの検索対象を指定します。
R:\crate_type> rustc main.rs -L rlib
R:\crate_type> main.exe
255
難なく実行できました。
Rust から dylib を呼んでみる
続いて dylib です。
R:\crate_type> rustc main.rs -L dylib
error: cannot satisfy dependencies so `std` only shows up once
|
= help: having upstream crates all available in one format will likely make this go away
error: cannot satisfy dependencies so `core` only shows up once
|
= help: having upstream crates all available in one format will likely make this go away
~~~~~~~~ 省略 ~~~~~~~~
error: cannot satisfy dependencies so `panic_unwind` only shows up once
|
= help: having upstream crates all available in one format will likely make this go away
error: aborting due to 13 previous errors
まずは二重定義だと怒られました。これから std をコンパイルしようというのに、依存元に既に含まれるのは確かに変ですね。
それならばということで、ここで prefer-dynamic を登場させましょう。
R:\crate_type> rustc main.rs -L dylib-dyn
R:\crate_type> main.exe
R:\crate_type> echo %errorlevel%
-1073741515
コンパイルは通りましたが、実行時にエラーが出ました。なお -1073741515 (0xc0000135) は STATUS_DLL_NOT_FOUND です。
答えは .exe をエクスプローラから起動すると出ます。
![grafik.png](https://cdn.silicloud.com/blog-img/blog/img/657d5e5037434c4406cf4db2/57-0.png)
それはそうですね。私の場合は rustup を使用しているので、下記の場所にありました。
R:\crate_type> copy C:\Users\etoilevi\.rustup\toolchains\stable-x86_64-pc-windows-msvc\bin\std-f87c887dcbebcf7e.dll
1 個のファイルをコピーしました。
R:\crate_type> main.exe
255
なんとか動きました。
Rust から cdylib, staticlib を呼んでみる
続いてシステムライブラリの cdylib, staticlib を呼んでみます。
R:\crate_type> rustc main.rs -L cdylib
WARN rustc_metadata::locator no metadata found: no `.rustc` section in '\\?\R:\crate_type\cdylib\add.dll'
WARN rustc_metadata::locator no metadata found: no `.rustc` section in '\\?\R:\crate_type\cdylib\add.dll'
error[E0462]: found staticlib `add` instead of rlib or dylib
--> main.rs:1:1
|
1 | extern crate add;
| ^^^^^^^^^^^^^^^^^
|
= help: please recompile that crate using --crate-type lib
= note: the following crate versions were found:
crate `add`: cdylib\add.dll.lib
error: aborting due to previous error
R:\crate_type> rustc main.rs -L staticlib
error[E0462]: found staticlib `add` instead of rlib or dylib
--> main.rs:1:1
|
1 | extern crate add;
| ^^^^^^^^^^^^^^^^^
|
= help: please recompile that crate using --crate-type lib
= note: the following crate versions were found:
crate `add`: staticlib\add.lib
error: aborting due to previous error
どちらも同じ理由で怒られます。これは生成物の中に rustc が使用するメタデータが含まれておらず、関数の型などが分からないためです。dylib にあった .rustc セクションのことです。
ということで外部関数の宣言を追加します。
mod add {
#[link(name = "add.dll")] // cdylib のみ
extern "system" {
pub fn saturating_add(a: u8, b: u8) -> u8;
}
}
fn main() {
unsafe {
println!("{}", add::saturating_add(200, 200));
}
}
再度コンパイルしてみましょう。
R:\crate_type> rustc main.rs -L cdylib
R:\crate_type> main.exe
255
~~~~ ここで #[link(name = "add")] をコメントアウト ~~~~
R:\crate_type> rustc main.rs -L staticlib -l add
R:\crate_type> main.exe
255
実行できました。
C から cdylib を呼んでみる
ところで cdylib には標準ライブラリそのものは入っていませんでしたが、ちゃんと機能するのでしょうか。
以下のような C のコードを書いて、さきほど作った DLL の関数を動的に呼び出してみましょう。
#include <stdio.h>
#include <Windows.h>
typedef UINT8(CALLBACK *SATURATING_ADD)(UINT8, UINT8);
int main()
{
HMODULE hLib;
SATURATING_ADD pProc;
UINT8 ab = 200;
hLib = LoadLibraryA("add");
if (hLib == NULL)
{
printf("%u", GetLastError());
return 1;
}
pProc = (SATURATING_ADD)GetProcAddress(hLib, "saturating_add");
if (pProc == NULL)
{
return 2;
}
printf("%u\n", pProc(ab, ab));
return 0;
}
実行してみましょう。
R:\crate_type>main.exe && echo exitcode: %errorlevel%
255
exitcode: 0
saturating してます。正常に動作しているようです。
ちなみに dylib でもシンボルはエクスポートされているため、同様に動きます。
まとめ
以上 Rust のライブラリ用 crate_type の違いを、生成物の中身とともに確認してみました。
最終的な実行ファイルまでを Rust だけで開発する場合には Cargo を通したビルドのみで事足ります。ですので最初に「Rust ライブラリ」と書いてあったものについては、あえて指定する機会もないと思います。
最終品が実行ファイルでない、つまり C やその他の言語から Rust の関数を呼ぶ際には、動的リンク/ロードでよければ cdylib の、musl を使用するなど動的な依存ができない場合は staticlib の指定になると思います。
以上、開発のお役に立てれば幸いです。
おまけ (WebAssembly)
ちなみに WebAssembly のライブラリを作成する際、初期状態の Cargo.toml でビルドすると
crate-type = ["cdylib", "rlib"]
と書けと怒られますが、これについても「おまじない」ではなくなりましたね。
ここでの cdylib は最終成果物の .wasm ファイル(=動的ライブラリ)を、rlib は Cargo が内側で使う .rlib ファイルを生成します。