6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

僕がRustを推したい理由

Posted at

どうも。暇すぎてミイラになった人です。

最近、というよりここ数年は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ならどうなるでしょうか?

というわけで検証してみたコードがこちら:

main.go
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では全ての値は必須である という点です。つまり、nilNULLをArcにぶち込むなんてことはできないのです。そしてそもそも、Rustではunsafeを使わない限り、nilNULLの値が存在することもできません。

この上で、上記のコードを見ると、Result型以外の型の値を返すことはなく、かつtextにぬるぽが混入することもありません。また、エラーが発生した時にどういう処理を行うのかを明確に記述しなければ、正常時の値を獲得することもできないため、このResult一つとっても、Rustのメモリ保護の機構が秀逸である事が分かるかと思います。

Rustでnilに相当するものはあるのか

ここで読者諸君は疑問に思われるかもしれません。nilが必要な場面があるが、その場合はどうすべきか、と。

ご安心ください! ちゃんとあります。ただし、この場合もOptionの値は必須になるので、指定された型以外の型が混入するという謎現象はありません。

まとめ

どの言語が素晴らしいかなどと優劣はつけられません。たとえば、コーディングテストなどではかき捨てで使えるスクリプト言語が向いています。特に、僕はPythonをかき捨てのためのスクリプト言語として今も利用しています。現代のWebプラットフォームのバックエンド部分ではGo言語が良く使用されるでしょう。Rustはメモリ保護がずば抜けている一方で、そのメモリ保護が原因でコードが煩雑になる事があります。

なので、使いたいプログラミング言語はケースバイケースで選ぶべきでしょう。

おわり

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?