0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Rust] コンパイルエラーを日本語化する

Posted at

はじめに

おはようございます。今回は、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の勉強が捗るのではないか、とも思いました。

「いやいや、エラーメッセージを日本語化するなんて言語道断!
英語だからちゃんと意味が伝わって、みんなが理解できるし、それが良さなんだよ!」

そう思ったあなたは、おそらくこの記事のメインターゲットではありません。回れ右してください。

できたもの

あんまりダラダラしてると苛つかせてしまうので、先に結果を貼っておきます。

image.png

image.png

今のところ、rust-analyzerによるVSCode上でのエラー表示と、cargoによるエラー表示を日本語化できました。

しくみ

Rustには、rustc-wrapperという機能があります。

RUSTC_WRAPPER — Instead of simply running rustc, 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で実行結果を書き換えるというのは、もしかしたらすべきではないかもしれません。

なので、cargorustcを呼び出すときに、まず自作のrustcラッパーを呼び出させるようにします。
ざっくりいえば、今までこれが実行されていたのが...

$ rustc (some parameters ...)

こうなります:

$ my-wrapper rustc (some parameters...)

my-wrapperは第1引数(通常はrustcの実行ファイルへのパス)を、渡されたパラメーターを渡して実行し、その出力を返すことが期待されています。
my-wrapperrustcの出力を精査し、日本語化したうえで返すようにすればいいんじゃね、というのが今回の試みでした。

やってみた感想

結果としては、そこそこ大変でした。しかも一番やりたかったVSCodeのrust-analyzerの結果表示は比較的簡単で、ターミナル出力を日本語化するほうが大変でした。

なぜかというと、rustcは以下のような形式で結果を投げてきやがります:

{
  "$message_type": "diagnostic",
  "message": "borrow of moved value: `s1`",
  "code": {
    "code": "E0382",
    "explanation": "(このエラーについての説明)"
  },
  "level": "error",
  "spans": [], // 省略
  "children": [], // 省略
  "rendered": "(実際にcargoが画面に表示するテキスト)"
}

なんと、実際の診断結果が出る前の段階で、rustccargoが出力すべきテキストをすでに決定してしまっているのです。
これは、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-analyzerspanschildrenに保存されているlabelを見ているので、そこだけさくっと差し替えれば問題ありませんでした)

また、(私の探し方が悪いのかもしれませんが)rustcラッパーに関する情報が全然見つかりませんでした。かなりAIと相談しながら進めていた気がします...
これってつまり、現行のrustcが完璧すぎるから、ラッパーなんてなくてもいいっていうコミュニティの総意なんですかね...??

ソースコード

(およそ人様に見せられるようなコードをしていないので、もっといい感じになったら公開します...タブン...)

今後やりたいこと

  • 翻訳をファイルに切り出す(今のところはハードコード)
  • 翻訳をもっと充実させる
  • renderedの差し替えも頑張る
  • ツールを公開する!

まとめ

情報量の欠片もありませんでしたが、読んでいただきありがとうございました。
いずれ気が向いてコードがキレイになったら公開したいなーと思ってます。

それか、これを見てアイデアを得たそこのあなた、ぜひ私の代わりに作ってください。
アイデアはCC-0だと勝手に思ってる(問題発言)ので。

それではまた。

追記

RustのFerrisちゃんかわいくないですか??????????
rustacean-orig-noshadow.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?