早くて安全、以外のRustの好きなところ
こんにちは。Rustを愛し、Rustに愛された男です。
BoostDraftでは、一部のシステムでRustで使用しています。そこそこQPSのあるAPIサーバーや、そこそこ量のあるデータの解析などです。
Wikipediaでは、Rustの説明として以下のような一文から始まっています。
Rust(ラスト)は、性能、メモリ安全性、安全な並行性を目指して設計された
引用元:Rust (プログラミング言語)
また公式サイトでは、Rustを使う理由として「パフォーマンス」「信頼性」「生産性」が挙げられています。
早くて安全!にフォーカスされがちなRustですが、僕がRustを使う一番の理由は「生産性」です。
なので今回は、生産性に寄与しそうな、僕のお気に入りポイントをいくつか紹介したいと思います。
エコシステム
Cargo
CargoはRustにおけるビルドシステム兼パッケージマネージャです。
ビルドシステムもパッケージマネージャも一般的なものですが、Rustのコミュニティからオフィシャルに提供されているので、安心感があります。
crates.io, docs.rs
Rustではライブラリのことをcrateと呼びます。公開されているcrateはcrates.ioで一元的に管理されてます。
crateを公開する際には、ドキュメントが自動で生成され、docs.rsに統一されたフォーマットで公開されます。
他の多くの言語では、ライブラリのコミュニティが独自に作成したドキュメントである場合も多く、フォーマットもバラバラです。
フォーマットが統一されているというのは、利用者にとってとてもありがたいことです。
またRustは文化的にドキュメントを重要視しており、人気のあるcrateのドキュメントのクオリティは非常に高いです。
Doc commentsでは、ドキュメント内にコードを書き、それをテストとすることもできます。
rustfmt, clippy
rustfmtは名前の通りフォーマッタですが、オフィシャルに提供されているということで、選択に悩む必要がありません。
旧知のプログラマーと飲む時は、インデントはタブとスペースどちらが優れているか毎回議論になるわけですが、実際のところ優れているのは「統一されていること」です。
clippyはいわゆるlinterです。これもオフィシャルです。オフィシャルなlinterというのは珍しいのではないでしょうか。
フォーマッタやlinterって導入が面倒なイメージがあります。それぞれ設定ファイル作らなければいけなかったり。
rustfmtやclippyはrustをインストールしたらもう使えます(設定ファイルを使用することもできます)。
言語
スコープを小さくするツール
プログラマーにとってグローバル変数は禁忌ですよね。壊れやすいのはもちろんですが、壊れた時の調査をとても難しくします。
でもそれってグローバル変数だけなのですか?僕は、どんなものもスコープは狭ければ狭いほどいいと考えています。ミュータブルなら尚のことです。
Rustにはそれを助けてくれる機能があります。
値を返すブロック
Rustのブロックは値を返すことができます。
let x = {
2
};
assert_eq!(x, 2);
現実的な例として、sqlxでコネクションプールを作るコードを書いてみます。
#[tokio::main]
async fn main() {
use std::env;
let host = env::var("DATABASE_HOST").unwrap();
let user = env::var("DATABASE_USER").unwrap();
let password = env::var("DATABASE_PASSWORD").unwrap();
let dbname = env::var("DATABASE_NAME").unwrap();
let database_url = format!("postgres://{}:{}@{}/{}", user, password, host, dbname);
let _pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await
.unwrap();
}
接続のための情報であるhost
やuser
はコネクション作成後は不要ですが、この関数のスコープでは存在し続けてしまいます。
ブロックを使うことで、スコープを最小限の範囲にすることができます。
#[tokio::main]
async fn main() {
use std::env;
let pool = {
let host = env::var("DATABASE_HOST").unwrap();
let user = env::var("DATABASE_USER").unwrap();
let password = env::var("DATABASE_PASSWORD").unwrap();
let dbname = env::var("DATABASE_NAME").unwrap();
let database_url = format!("postgres://{}:{}@{}/{}", user, password, host, dbname);
sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await
.unwrap()
};
}
値を返す式
Rustでは、ifやloopも値を返すことができます。
値を返すifは三項演算子のようにも使えますが、三項演算子と違って複数の条件で分岐したり、分岐内で多少の処理を書いても可読性を落としません。
let x = 10;
let y = if x < 10 {
100
} else if x < 20 {
1000
} else {
10000
};
println!("{y}");
この例ではブロックとloopを使って、ミュータブルな値のスコープを最小限にしています。
let count = {
let mut counter = 0u32;
loop {
counter += 1;
if counter == 5 {
break counter;
}
}
};
println!("{count}");
どこでも定義
ここまでは値のスコープに有用な機能を紹介しましたが、Rustでは定義も自由な位置で行うことができます。
fn func() {
fn inner_func() {
struct Innter {}
let closure = || {
const MAX: i32 = 999;
let inner_closure = || {
use std::str::FromStr as _;
i32::from_str("1")
};
};
}
}
再利用するものだけど、ある関数の中でしか使わないといった場合に、スコープを最小限にすることができます。
明示的
Rustの値はデフォルトでイミュータブルです。ミュータブルにする場合はmut
をつけます。ミュータブルな値は、所有権のシステムにとって特別な意味を持ちます。
しかし僕にとっては、引数の宣言でミュータブルが明示的であることに価値を感じます。
これは素朴なJavaScriptの関数です。
const normalFunction = (obj) => {
obj.x = 'hello'
}
この関数にオブジェクトを渡すと、ひどい目に遭うことでしょう。
悲劇を避けるためには、自分が呼び出した関数が値を変更するのかどうか理解しておく必要がありますが、それは現実的ではありません。
結局、運に任せるしかありません。
Rustでは関数が値を変更するときは、mut
を付けて受け取る必要があります。そして呼び出し側はmut
をつけた値を渡す必要があります。
値が変更されることを知っていれば、あらかじめ用心することもできますね。
mut
だけでなく、参照を渡すのか値を渡すのかも明示的です。Result
/Option
によって、異常系の処理も必然的に明示的になります。match式も全てのパターンを網羅する必要があります。
明示的というのは、それだけでプログラマーから恐怖から守ってくれます。
まとめ
充実したエコシステムやユニークな言語の機能で、プログラマは自分のコードに自信を持ち、目的に集中することができます。
早くて安全!も素晴らしいですが、楽しく快適!な側面も認知されたらいいのになと思います。
Discussion