はじめに
おはようございます。今回は、Rustというプログラミング言語のコンパイルエラーを日本語化しよう、ということでやってみました。
半分備忘録のような記事になってしまいましたが、もしよければお付き合いください。
きっかけ
(Rustaceanの方々にとっては当たり前の内容を含みます。)
Rustには、とても強力でとても難しい、RustをRustたらしめている所有権システムが存在します。
その本質についてここでは紹介しませんが、他の言語にはない新しい概念ゆえ、Rustを触ったことがない私には難解なものでした。
しかし、Rustには素晴らしい点があります。親切なエラーメッセージです。
以下のコードを見てください:
fn main() {
println!("Hello, world!");
let s1 = String::from("Hello, ");
let s2 = s1;
println!("{}", s1);
println!("{}", s2);
}
所有権のエラーを出すお手本のようなコードです。
所有権システムについて少しでもかじったことがある人は、
let s2 = s1;
この段階で、s1
の所有権がs2
に移動(ムーブ)することで、
println!("{}", s1);
このコードがコンパイルできない、ということがわかるでしょう。
Rustのコンパイラはとても優しく、以下のようなエラーメッセージを出力します:
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:20
|
3 | let s1 = String::from("Hello, ");
| -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
4 | let s2 = s1;
| -- value moved here
5 | println!("{}", s1);
| ^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
4 | let s2 = s1.clone();
| ++++++++
For more information about this error, try `rustc --explain E0382`.
ざっくり日本語訳するとこうなります:
error[E0382]: 移動された値の借用: `s1`
--> src/main.rs:5:20
|
3 | let s1 = String::from("Hello, ");
| -- `s1` は `Copy` トレイトを実装しない `std::string::String` 型なので、移動が発生します
4 | let s2 = s1;
| -- 値はここで移動されました
5 | println!("{}", s1);
| ^^ 移動後、値はここで借用されました
|
help: パフォーマンスコストが許容できる場合、値のクローンを検討してください
|
4 | let s2 = s1.clone();
| ++++++++
このエラーについての詳細は、`rustc --explain E0382` を実行してください
わかりやすい!!
エラーの内容だけでなく、エラーの原因、つまりなぜ起こったのか、そして今回のケースでは修正方法も教えてくれます。
こんなにエラーが親切なら、いっそのこと日本語で出してくれたらいいのに...と思ったのが、日本語化しようと思ったきっかけです。
DX (Developer Experience) って大切ですからね。
また、Rustのエラーを日本語化できるほどにまで熟読することで、よりRustの勉強が捗るのではないか、とも思いました。
「いやいや、エラーメッセージを日本語化するなんて言語道断!
英語だからちゃんと意味が伝わって、みんなが理解できるし、それが良さなんだよ!」
そう思ったあなたは、おそらくこの記事のメインターゲットではありません。回れ右してください。
できたもの
あんまりダラダラしてると苛つかせてしまうので、先に結果を貼っておきます。
今のところ、rust-analyzer
によるVSCode上でのエラー表示と、cargo
によるエラー表示を日本語化できました。
しくみ
Rustには、rustc-wrapper
という機能があります。
RUSTC_WRAPPER
— Instead of simply runningrustc
, Cargo will execute this specified wrapper, passing as its command-line arguments the rustc invocation, with the first argument being the path to the actual rustc.
https://doc.rust-lang.org/cargo/reference/environment-variables.html
つまりrustc
を呼び出すとき、このラッパーを通してrustc
を呼び出すようCargoに指示できる機能です。
本来(?)はsccache
などのビルドキャッシュツールを使うためのものらしいのですが、これによってrustcの出力を仲介・翻訳して返すことができるのではないかと考えました。
RUSTC_WRAPPER
で実行結果を書き換えるというのは、もしかしたらすべきではないかもしれません。
なので、cargo
がrustc
を呼び出すときに、まず自作のrustcラッパーを呼び出させるようにします。
ざっくりいえば、今までこれが実行されていたのが...
$ rustc (some parameters ...)
こうなります:
$ my-wrapper rustc (some parameters...)
my-wrapper
は第1引数(通常はrustcの実行ファイルへのパス)を、渡されたパラメーターを渡して実行し、その出力を返すことが期待されています。
my-wrapper
がrustc
の出力を精査し、日本語化したうえで返すようにすればいいんじゃね、というのが今回の試みでした。
やってみた感想
結果としては、そこそこ大変でした。しかも一番やりたかったVSCodeのrust-analyzer
の結果表示は比較的簡単で、ターミナル出力を日本語化するほうが大変でした。
なぜかというと、rustc
は以下のような形式で結果を投げてきやがります:
{
"$message_type": "diagnostic",
"message": "borrow of moved value: `s1`",
"code": {
"code": "E0382",
"explanation": "(このエラーについての説明)"
},
"level": "error",
"spans": [], // 省略
"children": [], // 省略
"rendered": "(実際にcargoが画面に表示するテキスト)"
}
なんと、実際の診断結果が出る前の段階で、rustc
はcargo
が出力すべきテキストをすでに決定してしまっているのです。
これは、cargo check
で表示されるテキストは、このmessage
とかcode
とかlevel
とかを無視し、すべてこのrendered
によって決定されている、ということを表します。
rendered
には色付けのためのANSIエスケープシーケンスや、フォーマット用の記号など含まれているので、ここを日本語化するのはかなり骨が折れました。
気になる人のために、rendered
がどんな感じになっているかを添付しておきます:
実際のrendered
\u001b[0m\u001b[1m\u001b[38;5;9merror[E0382]\u001b[0m\u001b[0m\u001b[1m: borrow of moved value: `s1`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/main.rs:5:20\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m3\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let s1 = String::from(\"Hello, \");\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mmove occurs because `s1` has type `String`, which does not implement the `Copy` trait\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m4\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let s2 = s1;\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mvalue moved here\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m5\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m println!(\"{}\", s1);\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;9m^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;9mvalue borrowed here after move\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;14mhelp\u001b[0m\u001b[0m: consider cloning the value if the performance cost is acceptable\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m4\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m let s2 = s1\u001b[0m\u001b[0m\u001b[38;5;10m.clone()\u001b[0m\u001b[0m;\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[38;5;10m++++++++\u001b[0m\n\n
最終的には、翻訳する段階でrendered
を自前のものに差し替えるということで手を打ちました。が、かなり不安定で細かいところを見ればrustc
とはフォーマットが異なるため、ターミナル出力の日本語化は諦めたほうがいいかもしれません。
(rust-analyzer
はspans
やchildren
に保存されているlabel
を見ているので、そこだけさくっと差し替えれば問題ありませんでした)
また、(私の探し方が悪いのかもしれませんが)rustcラッパーに関する情報が全然見つかりませんでした。かなりAIと相談しながら進めていた気がします...
これってつまり、現行のrustcが完璧すぎるから、ラッパーなんてなくてもいいっていうコミュニティの総意なんですかね...??
ソースコード
(およそ人様に見せられるようなコードをしていないので、もっといい感じになったら公開します...タブン...)
今後やりたいこと
- 翻訳をファイルに切り出す(今のところはハードコード)
- 翻訳をもっと充実させる
-
rendered
の差し替えも頑張る - ツールを公開する!
まとめ
情報量の欠片もありませんでしたが、読んでいただきありがとうございました。
いずれ気が向いてコードがキレイになったら公開したいなーと思ってます。
それか、これを見てアイデアを得たそこのあなた、ぜひ私の代わりに作ってください。
アイデアはCC-0だと勝手に思ってる(問題発言)ので。
それではまた。