CLOSE

Rust完全に理解した(嘘)_LT1(全1記事)

トレイトの実体を捉えれば、Rustへの恐怖は少し減る コンパイルエラーの対処で理解できた言語のコンセプト

キャディ株式会社が主催した「Rust完全に理解した(嘘)」。バックエンドエンジニアたちが Rustを習得するまでの苦労や、使ってみたうえでの技術的なメリット・デメリット・課題などについて話しました。ここで登壇したのは、冨田陽介氏。Rustの抽象化プログラミングにおいて発生したコンパイルエラーの対処について発表しました。

自己紹介

冨田陽介氏:私は「コンセプトから理解したいRust(願望)」について紹介したいと思います。

(スライドを示して)自己紹介はザッと流しますが、1つだけ伝えたいことがあるとすると、私自身はこんな感じで、これまでのキャリアから少し離れている領域でRustに携わっているので、自分がチャレンジしたいと言うと、その領域を任せてもらえる会社であるかなと思います。

もしも「キャディという会社に興味があるけれど、Rustをやったことないしなぁ」という感じで迷っている方がいたら、ぜひカジュアル面談やほかのイベントに参加してもらえるとうれしいです。このイベントもその1つのきっかけになるとうれしいなと思っているので、そこだけはお伝えしたいです。

Rustを勉強する中で感銘を受けた本

では、Rustの話に入っていきます。私自身はこの会社に入るまでRustに関わっていなかったので、キャディに内定してはじめてRustを勉強しないと思ってやり始めた感じです。

(スライドを示して)「形から入るマン」ということで、いろいろな本や、いろいろなUdemyのコースを買ったりやったりしました。まぁ、やっていないのも何個もあるのですが(笑)、そういう感じでいろいろと試していました。

その中で1つを誰かにおすすめするとしたら何がいいかなと考えたところ、この『コンセプトから理解するRust』という本だなと思いました。最近の本で、この本に出会ったのはキャディに入社してしばらく経ってからなのですが、すごくよかったと思います。今回のLTのタイトルも、この本からもらってつけました。

(スライドを示して)この本の表紙に「エラーメッセージをよく読み、所有権の感覚をつかみ、豊富な型に精通し、トレイトの実体を捉えれば、Rustはもう怖くない」と書いてあります。私はまだそう言えないので、言えるようになりたいです。Rustをやっていて、最初の「わからんわからん」の時に、先輩社員に教えてもらいながら、いろいろな実装をする時間を取ってもらいました。その時にやっていたことやいろいろ教えてもらったことがまさにこれで、メチャクチャうまくまとめた言葉だなと感銘を受けました。

「Rustの抽象化プログラミング」の章を読んで、自分もやってみようと決意

ということで、よい本だなと思いながら5章まで書籍を読み進めていると、「Rustの抽象化プログラミング」という章が出てきました。

(スライドを示して)あまり深くは説明はしきれないので、サッと流していきますが、絶対値を返す関数の例が出ています。これはi32が対応で「impl IAbs for i32」という型がハードコードされた感じで、これをi8やi16など、ほかの型でも動くようにしたい場合です。この実装をコピペして、型の定義を足していけばいいのですが、そうではなくてトレイトのデフォルト実装を満たしてやりたいというサンプルになっています。

(スライドを示して)コンパイルできるように修正したものが次で、いきなりこのような感じでかなり改良されたソースコードがボーンと出てきました。

見比べてみると、大きく行が増えていて、難しそうなトレイト境界がメチャクチャたくさん書かれています。

さらに読み進めていくと、「筆者も一発でこのコードを書いたわけではありません。VS Codeに表示されるエラー情報、コンパイルした時のエラー情報を見ながらこのコードを作りました」と書いてありました。うーん。でも、やはり自分で書ける気があまりしないなという感じがすごくあったので、これはとりあえずやってみるといいのではないかと、やってみたのが、このLTのネタになっています。

時間の関係上、ここではすごくサクサクと説明したので、とても簡単に理解したと思われる方もいるかもしれませんが、実際にはしっかりと時間がかかってやったものなので、そういうディスクレームを最初にしておきたいと思います。

「“Self” cannot be known at compilation time」

(スライドを示して)とりあえずやりはじめということで、まずトレイトのデフォルト実装に先ほどのやつをそのままボーンとコピペで持ってきました。

スライドに書いてあるとおり、きちんとコンパイルエラーになって怒られました。「“Self” cannot be known at compilation time」ということで、「stack領域に置くものに関してはコンパイルのタイミングでサイズがわかっているものじゃないとダメだよ」というのは、Rustの本を読んだり、ちょっと触ったりしたことがある人はけっこうよくぶつかるところかなと思います。ここでは、コンパイラがやさしく緑色で「これを足しなよ」と提示してくれたので、そのトレイト境界を追加しました。

(スライドを示して)この「Sized」というトレイトについてのドキュメントなども見たりしながら本を見てみると、「基本は暗黙的に付与されるものではあるけど、Trait Objectではそうではない場合があるから、必要になることもあるよ」というようなことが書いてあったて、1つRustに詳しくなりました。

「>=」というoperationはできない

(スライドを示して)コンパイルエラーが変わって、次は「>=」というoperationはできませんよと言われています。大小比較したい時には、OrdやPartialOrd的なTraitが必要というのも、Rustの本で見たことがあります。これも一番下の行に「PartialOrdのトレイト境界を足すといいんじゃない?」というようなことが書いてあるので、足しました。

型が違う0とselfは比較できない

(スライドを示して)またコンパイルエラーが変わって、次は「0とselfを比較しているけど、型が違うから比較できないんじゃない?」と怒られています。

ここの0は暗黙的にi32になるのですが、ほかの型も入ってくる可能性があります。今回の場合だと絶対値を求める関数で、なんらかの整数型を想定したサンプルになっているので、i8、i16、i32の中の一番桁数の少ないi8からintoしてやればいいのではないかと、i8型をハードコードしてintoしています。

i8型から型変換できない型に対しての実装もありえるよなと思いましたが、そういう時の成約をどうやって出すのかがパッと出てこなかったので、ここではいったん保留して進めました。

−selfという書き方はできない

(スライドを示して)次はまたコンパイルエラーが変わって、「−selfと書いてあるけれど、そんな書き方はSelf型に対してはできないよ」と怒られています。

これまではトレイト境界の書き方を出してくれていましたが、ここだけコンパイラのエラーが突然シンプルになってしまいました。エラーメッセージでググっても、有益な情報にヒットせず、ここだけは書籍をカンニングしてしまいました。

unary operatorの「−」を使えるようになるには、NegというNegativeのトレイト境界を持つ必要があるということを学びました。Neg Traitについて調べてみると、それがimplementされているstdの型の一覧にi8やi16があって、「なるほど、そういうのがあるんだね」と勉強になりました。

SelfはFromのトレイトを持っていないとダメ

(スライドを示して)またコンパイルエラーが変わりました。i8型からintoしているけれど、そうではない型が来る可能性もあるよなと、気にしながらもすっ飛ばしたやつがきちんと怒られました。SelfはFromのトレイトを持っていないとダメだよと怒られたので、それもそのまま追加しました。Fromトレイトに関してもドキュメントがあったので、それを読んでふむふむと改めて勉強しています。

SelfからOutput型の型変換は、asではできない

(スライドを示して)次は、またコンパイルエラーが変わって、今回の関数ではi32であればu32にするようなかたちで戻り値の型が変わっているのですが、SelfからOutput型の型変換は、asではできないよと言われています。

型変換なのでintoで変換してやればいいのかなとも思うのですが、「i32からu32だとマイナスの値のまま変換して失敗することもありそうだよなぁ」と頭をよぎりました。ドキュメントを見てみると、ほかのi16やi32は全部同じでしたが、i8からu8への型変換はTryFrom/TryIntoになっているので、TryIntoでの型変換が行えるトレイト境界を追加しました。try_intoすると型変換してResultで返ってくるのですが、ここのロジックにおいてはマイナス値のチェックをして、プラスの値になるかたちでtry_intoしていて失敗しないはずなので、そのままunwrapしています。

トレイト境界が満たされていない

(スライドを示して)次がちょっとややこしかったのですが、unwrapしたあとのエラーの型が、「<Self as なんとかなんとか::Error>」という型のトレイト境界が満たされていないと怒られました。

これも結果的には、この一番下に書いてあるトレイト境界の定義を足してやったのですが、こういうResult型の中の、さらにその中のエラーに対してのトレイト境界みたいなものも適切に定義してやる必要があることや、エラーに対して、Errorという型であれば、Debugトレイトを持っている必要があるということを学べました。

ちょっと時間が押してきたので何個か飛ばします(笑)。

Result Error型のエラーにトレイト境界を追加する必要がある

(スライドを示して)ということで、最後に、もう1個、同じようにエラーの型に対してコンパイルエラーが出ました。Result Error型のエラーにトレイト境界を追加してやる必要があるよというエラーです。すごく長い文字列ですが、これ自体は単純にこのコンパイラのエラーの最後のサジェスチョンをそのまま貼っただけです。上のものと見比べてみると、少し意味も見やすいかなと思ったりします。

ということで、ここまでたどり着いて、コンパイルエラーなしで走って、プログラムも想定どおり動きました。一部カンニングはしましたが、本当に筆者の言ったとおり、無事に到達できました。

トレイトの実体を捉えられれば、Rustはちょっとだけ怖くなくなる

(スライドを示して)これは先ほどの本の文言の繰り返しになりますが、「エラーメッセージをよく読んで、トレイトの実体を捉えることで、Rustがちょっとだけ怖くなくなった」という話でした。やってみるとおもしろそうだなと思った方がいたら、ぜひやってみるといいかなと思います。先ほどの本もすごくおすすめです。

私からは以上です。ありがとうございました。

続きを読むには会員登録
(無料)が必要です。

会員登録していただくと、すべての記事が制限なく閲覧でき、
著者フォローや記事の保存機能など、便利な機能がご利用いただけます。

無料会員登録

会員の方はこちら

関連タグ:

この記事のスピーカー

同じログの記事

コミュニティ情報

Brand Topics

Brand Topics

  • 今までとこれからで、エンジニアに求められる「スキル」の違い AI時代のエンジニアの未来と生存戦略のカギとは

人気の記事

新着イベント

ログミーBusinessに
記事掲載しませんか?

イベント・インタビュー・対談 etc.

“編集しない編集”で、
スピーカーの「意図をそのまま」お届け!