Rust の型システムが示唆するもの

ReceivedEmail<Link> が来た

注: これは技術解説の記事ではない

先日、電話口でこんなことがあった:

〇〇社さんでしょうか?(若い女性の声)
    はい、どんなご用件でしょう

〇〇(株)と申しますが御社より資料請求をいただきまして、
ご案内の連絡を、~
    (聞いてないな)そうでしたか、~

今から御社Eメールアドレス宛に1通お送りしますがよろしいですか?
    分かりました。お願いします

(... メールが届く)

届きましたでしょうか
    はい
あぁ、届きましたか、よかった!では、一応の動作確認のため、
メール内のURLをクリックして、~(たくさん説明)
    (...? 違和感)あの、

はい。
    これ今クリックしなきゃいけないですか?

はい。でもこれは単なる動作チェックですので、機能を確認しましたらすぐに終わりますので
(さらにまくしたてる)
    いや、えっと、もし...もしも~し?
    (強引にしゃべり続ける相手に違和感が確信に)

...はい。
    そういうことじゃなくて、申し訳ないけど未確認のメールなのでURLクリックはできないです。
    最近その類のフィッシング増えてますし非常に危険ですよね。

...はぁ...。(沈黙)
    では確認がとれましたら改めて連絡しますので、ありがとうございました。失礼します。

で、上司に確認をとってみるとそんな会社は知らないし、資料請求なんてしていない、社内注意喚起へ、というオチ。

ちょいと知恵を使った(でも悪質な)営業の一種だろうし、懸念するほどの悪意はなかったかもしれないが、落ち着いて考えれば歴としたソーシャルエンジニアリングである。

相手の声や親しみやすい会話の流れによっては、ふと判断を間違えてクリックした先に、「PC乗っ取りました~」と出て、「乙」と電話が切れていた可能性もゼロではない。(ちなみに相手の電話番号は携帯電話のもの)

Rust 言語

まだ製品に使うには至っていないものの、Rust 言語に興味を持って触ってみている。 カニ本や実践系の本買って読んだり、目的を持って書く題材として Advent Of Code*1 にチャレンジしてみたりした。

それで、Rust にはしっかりした型システムがあって*2、 知る人ぞ知る歴史のあるものということで紹介は端折るとして、 例えば Option<T> という型があり、実に示唆に富んでいるように思う。

型システムによる制約と恩恵

Option<T> は、 T 型のデータがある Some(T) か、 あるいはそんなものは無い None の二択がセットになった列挙型で、

アントニー・ホーアという計算機科学者が「10億ドル単位の間違い」と後に反省した Null 値(空値の表現方法の一種)とは一線を画した、より望ましい表現方法である、というのは有名な話。

Null は値で None はコンテキスト

  • Null とは入れ物の中に入るデータである。

    • 空といいつつ入れ物から取り出せてしまう
    • それを実行時にCPUがさわって火傷する
  • Rust の None はデータとは別次元のコンテキストである。

    • 値なんてなかったという言語レベルでの状況を表す
    • コンパイラ用のメタ情報で実行時には存在しない

分けるべきものを一緒くたにしていると毎度そこで混乱が生じ、どこまでも問題がついて回るのは設計に漂う臭いと表現される。None は値が存在しない「状況」を、値とは別次元に切り離したことで問題から解放された。

これは分別をつけるとか、それをしっかり守ることの意義を示唆している。

機械の状態を言語レベルのコンテキストに

Rust で書いていると Option<T> は結構いたるところに現れて、その都度 None だった場合の手続きが必要になってしまうことを表面化してくれる。それに従わずばコンパイルなし。

このような型制約はそこまで細かい粒度の言語ロジックばかりでなく、たとえば機械の状態をこれで上手く表現することで、ハードウェアが壊れないように正しいスイッチング手順を強制するデザインまで可能だという。 The Embedded Rust Book (日本語) - 静的な保証

Book にもある通り、機械を壊さないために逐一 if を使って、電流を流してよいか確かめてからアクションする、というのが素朴なプログラミング的発想だが、Rust は一味違ってそんなチェックコードを不要にする。

ラクリを有体に言うと、機械の各状態に対応する型を定義し、電流を流せる状態の型に対してのみ電流を流す関数を定義し、電流を流せない状態の型からその関数を呼ぶとコンパイルエラーになるのでそんなコードは書けないということ。これを読んだ当時は、その手があったかとカルチャーショックを受けた。

この時 if でCPUを回すような実行時チェックは一切無く、コンパイル後に強気な正しい手順の命令だけがポツンとあるという具合である。Rust ではこれを「ゼロコスト抽象化」と呼ぶ(カッコいい、強い(確信))

現実での応用

さて、冒頭のお話に戻り、実は Rust を触っていたおかげで(あるいは、Rust にさんざんコンパイルエラーでいじめられたおかげで)、会話中に違和感が走ったように思う。

届いたメールには次ようなきわどい属性が付与されていた:

  • 電話口の若い女性の確認のもと送られてきたメールである
  • こちらからの資料請求への返信であるらしい
  • 自分から送ってくださいと頼んでいる

よく訓練された会社員とか公務員なら、呼吸をするように躱しているところであろうが、本気の攻撃者がさらに用意周到にきわどい属性を用意してきても余裕を保っていられるだろうか。

しかし Rust 脳的には、このメールは ReceivedEmail<Link> という型であり、フィッシングが猛威を振るう昨今ではまず follow_link() など実装されないのである。

ReceivedEmail::FromConfirmed(Link) となって初めて follow_link() が通り、ReceivedEmail::FromUnknown では無慈悲にコンパイルエラー。*3 それはもうコンテキストが違いますから、気の毒だけど私にはどうしようもないのです。(その無慈悲さは先方にも伝わったようで強引なトークがピタリと止んだ)

つまり、Rust の型制約は人の会話に顕れるようなハイレベルなビジネスロジックにもすんなり溶け込んで暴れてくれそうだということで期待に胸が膨らんでいる。

*1:言語の勉強によく使っている。早解きもいいけど雰囲気やストーリーも味わいたい

*2:他にも同等の型システムを持っている言語がある

*3:Emotet はここを突破し得るから、さらに AttachmentConfirmed とか OfficeFile::WithoutMacro を考えることになる