はじめに
「Rustの標準ライブラリは小さい」と言われます。実際、正規表現や乱数など多くの言語で標準ライブラリに入っているようなものが、Rustの標準ライブラリにはありません。こうなっている理由は「標準ライブラリに入っていなくても依存関係を簡単に追加できる」「後方互換性を保ちながら大きな標準ライブラリを維持するのは難しい」といったことが挙げられます。もちろん標準ライブラリが小さいと不便なこともあり「サードパーティライブラリの選択が難しい」というのはよく言われるところです。
ところでRustの標準ライブラリは実際に小さいでしょうか?小さいというと、どうしても低機能・できることが少ないというイメージになりますが、個人的な印象としては「Rustの標準ライブラリはカバー範囲は狭いが高密度」というものです。
あまりこういう観点で書かれたものは見たことがないので、この記事ではRustの標準ライブラリの "大きさ" について書いてみたいと思います。
整数型
Rustにおける組み込みの整数型は、一般的に使われるプログラミング言語の中でも、最も充実している方ではないかと思います。
まず組み込み型には8bitから128bitまでの符号つき・符号なし整数があります。128bit整数を持っている言語は結構珍しいのではないでしょうか。
次に整数型が提供するメソッドです。試しに u64
の提供するメソッドを列挙してみると、2021/12/1時点で87個ありました。
(うち21個はnightly)
borrowing_sub carrying_add carrying_mul checked_add checked_div checked
div_euclid checked_log checked_log10 checked_log2 checked_mul checked_neg
checked_next_multiple_of checked_next_power_of_two checked_pow checked_rem
checked_rem_euclid checked_shl checked_shr checked_sub count_ones
count_zerosdiv_euclid from_be from_be_bytes from_le from_le_bytes from_ne_bytes
from_str_radix is_power_of_two leading_ones leading_zeros log log10 log2
max_value min_value next_power_of_two overflowing_add overflowing_div
overflowing_div_euclid overflowing_mul overflowing_neg overflowing_pow
overflowing_rem overflowing_rem_euclid overflowing_shl overflowing_shr
overflowing_sub pow rem_euclid reverse_bits rotate_left rotate_right
saturating_add saturating_div saturating_mul saturating_pow saturating_sub
swap_bytes to_be to_be_bytes to_le to_le_bytes to_ne_bytes trailing_ones
trailing_zeros unchecked_add unchecked_mul unchecked_shl unchecked_shr
unchecked_sub unstable_div_ceil unstable_div_floor unstable_next_multiple_of
widening_mul wrapping_add wrapping_div wrapping_div_euclid wrapping_mul
wrapping_neg wrapping_next_power_of_two wrapping_pow wrapping_rem
wrapping_rem_euclid wrapping_shl wrapping_shr wrapping_sub
例えば加算であれば
checked_add
unchecked_add
saturating_add
wrapping_add
overflowing_add
-
carrying_add
(これはちょっと特殊)
がありますが、これらはそれぞれ加算結果がオーバーフローしたときの挙動が違います。もちろんオーバーフローを気にしないケースでは単に +
を使えばいいのですが、気にしたくなった場合にこういったメソッドが役に立ちます。
また、1が立っているビットの数を数えるcount_ones
のようなメソッドもあります。こういったメソッドはビット演算のテクニックを駆使して高速に実装できたり、CPUによっては専用命令が用意されていたりするのですが、それを自力で実装するのは大変面倒で、標準ライブラリに入っているととても助かります。
ポインタ型
Rustで生ポインタを使うケースは少ないですが、どうしても必要になる場合もあります。
ポインタ型に実装されているメソッドは2021/12/1時点で32個です。(うち6個はnightly)
そもそもポインタ型にメソッドが生えている言語自体珍しいのではないかと思います。
add align_offset as_mut as_ref as_uninit_mut as_uninit_ref cast copy_from
copy_from_nonoverlapping copy_to copy_to_nonoverlapping drop_in_place
guaranteed_eq guaranteed_ne is_null offset offset_from read read_unaligned
read_volatile replace set_ptr_value sub swap to_raw_parts wrapping_add
wrapping_offset wrapping_sub write write_bytes write_unaligned write_volatile
多いのはポインタ演算系ですが、低レイヤでよく出てくるのはmemory mapped I/Oへのアクセスに使われる read_volatile
/write_volatile
でしょうか。これはC系のvolatile
変数へのアクセスに相当します。
他にもC言語におけるmemmove
/memcpy
に相当するcopy_from
/copy_from_nonoverlapping
といったバッファコピー用のメソッドもあります。このような部分はほとんどのRustプログラマの目には触れない部分ですが、ここが充実していることで「Cと同等なパフォーマンスを出せる高速なコンテナ型」のようなものを、CのFFIではなくpure Rustで実装することができるようになっています。
文字列型
文字列型のメソッドは以下のリンクにあります。こちらは列挙しませんが、検索や分割・トリムなど他言語と比較しても同等か少し多めかな、という感じです。
Rustの文字列型はエンコードがUTF-8と決まっているので、Unicodeに関連したメソッドがいくつかあり、少し珍しいかもしれません。例えば、to_ascii_lowercase
はASCIIの範囲内のみの小文字化、to_lowercase
はUnicode定義に従った小文字化です。
let a = "Σ"; // ギリシア文字のシグマ(U+03A3)
dbg!(a); // a = "Σ"
dbg!(a.to_lowercase()); // a.to_lowercase() = "σ"
dbg!(a.to_ascii_lowercase()); // a.to_ascii_lowercase() = "Σ"
ちなみにこのΣですが、単語の末尾に登場するときだけ、小文字化するとσではなくςになるらしいです。to_lowercase
はそのあたりもケアしてくれますが、その分処理は重いはずなので、ASCIIの範囲内だけ小文字化すればいいならto_ascii_lowercase
の方が高速だということでしょう。
おまけ
最後にちょっと気になったので、いろいろな言語の標準ライブラリのソースコード行数を調べてみました。
異なる言語のソースコード行数比較はあまり意味がないですが、規模感くらいはわかるのではないかと思います。
厳密な比較は意図していないので、「テストが入っている・入っていない」「コンパイラのコードが入っている・入っていない」などの条件はバラバラです。(が、コンパイラのコードなどは通常それほど大きくないので大勢に影響はないと思います)
計測はRust製のコード行数測定ツール tokei で行いました。
言語 | 行数 | 測定範囲 |
---|---|---|
C++ | 272715 | https://github.com/microsoft/STL のC++/C++ Headerファイル |
Go | 2146844 | https://github.com/golang/go のGoファイル |
Python | 877043 | https://github.com/python/cpython のPythonファイル |
Rust | 313791 | https://github.com/rust-lang/rust のlibraryディレクトリ内のRustファイル |
Scala | 530092 | https://github.com/scala/scala のScalaファイル |
見てわかる通り、「Rustの標準ライブラリだけが他と比べて圧倒的に小さい」というようなことはなさそうです。どちらかというとGoが圧倒的に大きいですね。これは「Goの標準ライブラリはすごく充実している」という印象とも一致します。(Goはあまり高度な抽象化機能を備えていないのでコードが大きくなりやすい、というのも多少あるかもしれませんが)
まとめ
というわけでRustの標準ライブラリの大きさについて書いてみました。結局のところ、ここで紹介したような(悪く言えば重箱の隅をつつくような)メソッドが充実していても大半のプログラマにはあまり関係ないわけですが、Rustの標準ライブラリがどういう方向を目指しているのかを少し感じていただけるのではないかと思います。