Typescriptの次はRustかもしれない
Introduction
Rustを学び始めたのは多分今年の3月くらい。
なぜ急にRustに興味を持ったのかは後述しますが、半年ほどRustをいじくりまわしてみて、まだまだ学び途中でもRustの素晴らしさを語らずにはいられず、記事を書くことにしました。
Rustは「Cの正当な後継」なんてよく言われますが、WebエンジニアからみてもRustは本当に素晴らしいってことが少しでも伝わればいいなと思います。
RustとTypescript
「なぜRustとTypescript?」って思う方は多いと思うので、順に話していきたいと思います。
2020年のフロントエンドの流行
まず2020年今日時点のフロントエンドについて雑に書くと
- React・Vueが主流
- Typescriptがデファクト、babelなどでトランスパイルは言わずもがな
- サーバーサイドもNodeで書くならTypescript
- Jest、eslint、prettierをコミットフックやCIで回す
といったところですかね。
今年はDenoやRome、RecoilやBlitzなどの新しいツールやライブラリも多く出てて、新しい時代が近づいてる感を個人的には感じてますが、主流は去年までの延長線上にある気がしますね。
Typescriptは今年のStackOverflow調査で愛されてる言語ランキング2位にまでなってるので、来年とかにすぐ廃れるかって言われると流石にこの流行はしばらく続くんじゃないかなって思います。
フロントエンドの進化の歴史と今後
では今後ずっと10年とかTypescriptが流行続けるのかと言われると、まだ大きな変化はある気もします。
これまでフロントエンド界隈の進化をみるとこんな感じな気がするんですね。
- ES5の台頭:ブラウザごとに微妙に違うの辛いから仕様を決めよう!
- ES6の台頭:仕様決めてもサポートの問題もあるし辛いからJSからJSにコンパイルして吸収しよう!
- Reactの台頭:どうせコンパイルするならJSちょっと拡張しよう!
- Typescriptの台頭:コンパイルするなら型チェックもしちゃおう!
これに加え、WebAssembly(以下WASM)の登場によってブラウザで動く=JSという前提が崩れた今、コンパイル環境とか一々用意するくらいなら、もはやJSである必要もなくなりつつあるわけです。
そうなるとTypescriptが今後もずっとデファクトであり続ける保証ってないんじゃないかと僕は思い始めていました。
そこでWASMの大本命だったRustに注目し始めました。
Rustの人気
とはいえ、Cとかの代替なんて言われる言語にTypescriptばっか書いてる僕はさくっと手を出す気にはなれなかったんですが、徐々にRustの情報が増えていることに気付きました。
- MS、Google、AWSのRust採用・支援
- 愛されてる言語ランキング5年連続1位
- WASM
Typescriptが流行り始めた大きな転機だったんじゃないかと個人的に思ってるのは、GoogleのTypescript採用です。
「MS、Google、AWSが採用した」ことに加え「愛されてる言語ランキング5年連続1位」・・・
ここまで来るとWASM関係なしでもRustが気になり始め、「もしかしたら今後本当にTypescriptの代替になりうるかもしれない」と思い始め、Rustを学び始めることにしました。
RustをTypescriptと比較してみる
まずはRustとTypescriptの言語の特徴を比較してみます。
※TypescriptというかほとんどJavascriptとの比較になってますが、書き分け面倒なので一貫してTypescript呼びします。
圧倒的速度とメモリ安全
Rustはコンパイルしてバイナリを生成するのでランタイムを不要とし、ガベージコレクタなしでメモリ安全やメモリ最適な環境を構築できます(!)。
実際、実行速度は「CやC++に匹敵する」と言われており、ベンチマークなどみててもRustの速度はRubyやJSとは比較にならないほど速いです。
Typescriptで実行速度を気にするような場面はフロントだと非同期処理系か大量のDom操作とかくらいな気はしますが、サーバーサイドで実装することも多々あることを考えるとやはり速いに越したことはないですね。
関数型やオブジェクト指向的特徴
Rustは式指向言語で、デフォルトで変数がimmutableだったりパターンマッチや列挙型のサポート、イテレータなども持ってたりこの辺はTypescript以上に関数型っぽさを兼ね備えている気がします。
また、Rustは trait
というオブジェクト指向のポリモーフィズムを体現する仕組みも持ってたり、デコレータ的な感じでマクロという機構を持ってたりもします。
あとRustにはResult型(非同期処理周りでちょっと後述)やOptional型などのHaskellのモナド的な型があり、これらのメソッド群もよく考えられて作られています。
Typescriptでもこれらは自前である程度実装できるとはいえ、やはり組み込みであったほうが嬉しいんですがJS・TSの仕様の兼ね合いなどからやはり難しいんでしょうね・・・
これらの特徴はScalaの言う「オブジェクト指向と関数型の融合」という特徴に近い気がします。
一般的(?)にはやはり関数型言語はハードルが高い分、RustやScalaのこういったアプローチは受け入れやすさ・学習コストを実務の現実的なラインまで落としてくれる気がします。
モダンなDX
現代プログラミングにおいてユニットテストはなくてはならないものです。
Rustはテストコードをなんの用意もなしに実行できます。
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
#[test]
fn add_test() {
assert_eq!(add(1, 3), 4);
}
}
こう言った感じで同じファイルにテストを書けて、コンパイル時にプロダクションコードから削除してくれます。
実行も単に
cargo test
だけなので本当に簡単です。
lintやformatに関してもRustが公式で出してるものがあるので、ツールを入れれば以下のコマンドですぐ実行できます。
// format
cargo fmt
// lint
cargo clippy
Typescriptはまずコンパイルがts/babel/swc/esbuildなど多岐にわたり、テストはJest、Lintはes-lint、formatはprettierなどを用意する必要があります。
が、Lintやformatはルールが競合したり好みの問題が多々出るので設定がめんどい・・・。
昨今のDenoやRomeなどの新たなランタイムやオールインワンツールの登場は、こう言った細分化されたツールの統合をすることで高速な環境構築や学習コストの削減といった狙いが大きいと思います。
ただこれらのツールはあくまでOSSで、言語公式環境なわけではありません。
なので考えづらいかもしれませんが、Node環境がデファクトでなくなる可能性だってあります。
そういった面で言うとRustを書くならこう言った多岐にわたる選択肢がないので、悩む必要もないというのは非常に大きなメリットなのかなと思います。
非同期処理を比較
あと個人的にとても特徴的な差だと思ってる非同期処理周りについて比較したいと思います。
Rustの非同期処理はちゃんとすると正直よくわかってない書くこと多すぎるのでいろいろ省いてまずはTypescriptとの比較サンプル見ていただければと思います。
※やってることはほんと適当です。
Typescript
const maybeFailHello = (setting: boolean) => new Promise<string>((resolve, reject) => {
if (setting) {
resolve('hello')
} else {
reject(new Error('fail'))
}
})
const printAsyncHello = async (setting: boolean) => {
const greeting = await maybeFailHello(setting)
console.log(greeting)
}
Rust
async fn maybe_fail_hello(setting: bool) -> Result<String, dyn std::error::Error> {
if setting {
Ok("hello".to_string())
} else {
Err("fail")
}
}
#[tokio::main]
async fn print_async_hello(setting: bool) {
let greeting: String = maybe_fail_hello(setting).await?;
println!("{}", greeting);
}
こうやってみると全然違うんですが、なんとなくはわかりそうですね。
Typescriptではawaitを頭に付けるのに対し、Rustは.でつなげて書きます。
あと特徴的なのはTypescriptはご存知の通りPromiseをやりとりしますが、RustではResult型でやりとりしてます。
※正確にはRustのasyncはimpl Future
型を返すのですが、意識することも少ないしややこしくなるので割愛。
ここで面白いのが、Rustのawait?の書き方です。
?演算子は直前のメソッドでErrが帰ってきたらそのままその関数からErrを返します。
Rustでは回復可能なエラーと回復不可能なエラーを厳密に区別し、Errは回復可能なエラーとして扱います。
ただErrが来たら即時returnなんてめちゃくちゃあるあるなパターンですよね。
そこでRustはこの?演算子をErrの即時returnの糖衣構文としています。
面白いですよね、この書き方は型安全な世界だからこそ成り立つし回復可能なエラーだとわかるのも素晴らしい。。。
PHPだとassertとかあって契約プログラミングとか体現できてあれも面白いですが、Typescriptは絶対に落ちない非同期処理(タイマーとか)でもPromiseで扱ってしまうのがあまり好きではないので、この辺がRustだと解決されるのは良いですよね。
サーバーサイドRust
実際にWebサーバーやAPIサーバーとしてRustが使えるよってこともちょっと書きたいと思います。
actix-webがすごい
RustでWebフレームワークと言ったらRocketとactix-webです。
僕はactix-webが好きなのでここではactix-webのみ解説したいと思います。
actix-webは非常に軽量・高速で、以下の比較サイト(いまいち見方わかってないので間違ってたらすいません)では全ての言語のフレームワーク内でNo.2に入るくらい速いです。
またHTTPサーバーを含んでいるのでNginxやAppacheなしで動作し、rate limitや認証も簡単に導入できるうえ、テストも容易にかける優れものです。
Railsのようになんでもよしなに用意してくれるわけではないですが、かなり直感的にかけるので実装コストはやってみた感じは高くなかったように感じます。
堅牢性・速度・可読性やメンテナンス性どれをみても、とても素晴らしいフレームワークだと思います。
Dieselを使うとさらにすごい
RustのORMといえばDieselです。
内部実装あまり詳しくないので何ともですが、使った感じはほとんどクエリービルダーでした。
型がマクロ通してたりする分複雑で、コンパイルエラー時にメッセージみても何言ってるかわからないというのがちょっと辛いですが、それでもかなり使いやすかったですね。
マイグレーション時にマクロでテーブル定義とかを型化してくれるのはありがたいし、テスト時にテスト用のトランザクションかけてくれるのでテストは書きやすかったです。
実際にactix-web+dieselでAPI作ってみた
まだ作り途中ですが、これらを使ってAPIを作ってみました。
勤怠アプリの永続化用のマイクロサービスとして作ってるので機能は非常にシンプルです。
これまで述べた通り、低級言語とは思えないくらい簡単にAPIも作れます。
WebAssembly:ブラウザでRustが動く
WebエンジニアからみたRustを語る上でWebAssembly(以下WASM)ももちろん外せません。
WebAssemblyは近づいてる
WASMは今や多くのブラウザでサポートされており、Rust版React的な立ち位置のyewなど徐々にWebアプリケーションフレームワークなども登場しつつあります。
NPMモジュールの中身をWASMにすることもできます。
WASMを実務で導入してる企業はまだまだ少ないと思いますが、Typescriptは後方互換という大きな進化の妨げを抱え続けています。
そしてWeb技術は数年ごとに大きな変革を経ており、今後Typescriptが何かにとって変わられるとしたら、それはAltJSではなくWASMかもしれません。
もちろん僕がそうなって欲しいと思ってるからこんなこと書いてる部分は大いにあると思いますが、それでも今日Typescriptが将来何に置き換わるかと言われたら、WASMがそれを担う可能性は大いにあると思います。
まとめ
はっきり言って、僕は今後RustでWebAPIを作るのにRustのメモリ管理をゴリゴリに最適化して使って実装してけるイメージは湧いてないです。
僕はまだまだRust初学者で、それでもあえて言うならRustは素晴らしい言語です。
そしてTypescriptも素晴らしい言語だと思ってます。
今後しばらく、僕が作るWebアプリケーションの大半はまちがいなくTypescriptで書くことになると思います。
今後どのくらいTypescriptが流行り続けるのか、Rustが本当に流行るのかはわかりませんが、この記事を通してRustに少しでも興味を持ってもらえたら嬉しく思います。
あと最後に、ブログもやってるのでよかったら見てください!!!
Discussion