どうも。暇すぎてミイラになった人です。
最近、というよりここ数年はGoではなくRust推しな僕ですが、僕がなぜRustを推したいのか解説します。
この記事では他の言語のディスりが含まれています。PythonもしくはGoを使っていてRustが嫌いな人はアレしてください。
Pythonで例外を書いてみる。
def exc(text: str) -> str:
if len(text) == 0:
raise ValueError(text)
return text
これはtext
の長さが0なら例外をスローし、それ以外ならtext
を返すという、ごくごく一般的なPythonのコードです。そして僕も昔はPythonを推していました。しかし、今になってわかる。この書き方はダメだという事が分かります。例外をスローするという事は、str型以外の型が返されたうえで異常終了する可能性がある。 という解釈ができます。つまり、exc
の挙動を何も知らない人がこれを使うと、ValueErrorがスローされてサービスが落ちる、という惨状になりかねません。
Go言語で例外を書いてみる。
それじゃあ、今度は別の言語、例えばGo言語を使えばいいじゃないか、という考えに行き着きます。それでは以下のコードを見てみましょう。
import "errors"
func exc(text *string) (*string, error) {
if len(*text) == 0 {
return nil, errors.New(*text)
}
return text, nil
}
上のコードはPythonとほぼ同じ挙動をするコードです。ただし、パラメータは文字列のポインタ、返却値は文字列のポインタとエラーのタプルです。
一見すると、Pythonが抱えていた問題を解決しているように見えます。確かに、宣言された返却値の型以外の型が返ってくるという事は起きなさそうです。
ところが、今度は別の問題が浮上します。例えば、execに渡される型がnil
ならどうなるでしょうか?
というわけで検証してみたコードがこちら:
package main
import (
"errors"
"fmt"
)
func exc(text *string) (*string, error) {
if len(*text) == 0 {
return nil, errors.New(*text)
}
return text, nil
}
func main() {
value, err := exc(nil)
fmt.Println(*value, err)
}
そして出力されたものがこちらです。
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x47adb4]
goroutine 1 [running]:
main.exc(...)
/home/hyamamoto/test.go:9
main.main()
/home/hyamamoto/test.go:16 +0x14
つまり、ぬるぽが起きます。言い換えれば、サービスが落ちます。対処法はいろいろありますが、その何れもランタイムでnilチェックを行うものです。このため、プログラマがコード上で変数の値がnilになる可能性があるかどうかを注意深く観察しなければなりません。 ヒステリックまっしぐらです。
ちなみに、僕が体験した事例では、フォームバリデーションを実装した時に、サードパーティがエラーを出さずに正常な値としてnil
を出力する事例があって、それ故にサービスがよく落ちる事がありました。
Rustで例外を書いてみる。
それではRustではどうなるのかというと、以下のコードになります。
fn exc(text: Arc<String>) -> Result<String, String> {
return if text.is_empty() {
Err(text.to_string())
} else {
Ok(text.to_string())
}
}
Arc
は「共有リファレンス向けのスマートポインタ」です。さて、ここで注意すべき点は、Rustでは全ての値は必須である という点です。つまり、nil
やNULL
をArcにぶち込むなんてことはできないのです。そしてそもそも、Rustではunsafe
を使わない限り、nil
やNULL
の値が存在することもできません。
この上で、上記のコードを見ると、Result
型以外の型の値を返すことはなく、かつtextにぬるぽが混入することもありません。また、エラーが発生した時にどういう処理を行うのかを明確に記述しなければ、正常時の値を獲得することもできないため、このResult
一つとっても、Rustのメモリ保護の機構が秀逸である事が分かるかと思います。
Rustでnilに相当するものはあるのか
ここで読者諸君は疑問に思われるかもしれません。nil
が必要な場面があるが、その場合はどうすべきか、と。
ご安心ください! ちゃんとあります。ただし、この場合もOption
の値は必須になるので、指定された型以外の型が混入するという謎現象はありません。
まとめ
どの言語が素晴らしいかなどと優劣はつけられません。たとえば、コーディングテストなどではかき捨てで使えるスクリプト言語が向いています。特に、僕はPythonをかき捨てのためのスクリプト言語として今も利用しています。現代のWebプラットフォームのバックエンド部分ではGo言語が良く使用されるでしょう。Rustはメモリ保護がずば抜けている一方で、そのメモリ保護が原因でコードが煩雑になる事があります。
なので、使いたいプログラミング言語はケースバイケースで選ぶべきでしょう。
おわり