タプル小話
κeenです。最近、Rustのタプルに「tuple[0]
じゃなくて tuple.0
のようにアクセスするのなんでだろ」というのをみかけてびっくりしたのでそれについて。
TL;DR: タプルは構造体の一種。ユニット型は0要素タプル。
Rustのタプルの要素へのアクセスは tuple.0
のようにアクセスしたい要素の番号をドットで繋げるような記法で書きます。構造体のフィールドアクセスと似ていますね。
これについて、私のようにML系言語のバックグラウンドがある人は当たり前のように感じるでしょう。
しかし別の言語のバックグラウンド(PythonとかDとかがそうらしい?)を持つ人には驚かれるようです。
また、特段バックグラウンドがなくてもドキュメントで配列と比較するような書き方がされているのでそこからも疑問が湧くようです。
タプルとレコード
Rustだと分かりづらいのでRustが参考にしたというML系言語をみてみましょう。 ML系言語ではタプルとレコードは密接な関係があります。因みにレコードというのは構造体のようなものです。
例えばOCamlではレコードはラベルのついたタプルだとされています。
SMLでは逆に、タプルは特別なラベルのレコードだとされています。
これは実際に動かすと分かりやすいです。
2要素のタプルは ラベル 1
と ラベル 2
を持ったレコードです。
以下のようにタプル記法でもレコード記法でもタプルが作れます。
$ sml
Standard ML of New Jersey v110.79 [built: Tue Aug 8 23:21:20 2017]
(* タプル記法 *)
- (1, 2);;
val it = (1,2) : int * int
(* レコード記法 *)
- {1 = 1, 2 = 2};;
val it = (1,2) : int * int
どちらでも、 タプルはレコード(構造体)の一種なのです。
Rustのタプル
Rustのタプルも同様に、フィールド名がたまたま数字になっている無名構造体という扱いです。
Rustには無名構造体がタプルしかないので分かりづらいですが、名前ありのタプル構造体だとよくかります。
タプル構造体を フィールド 0
と フィールド 1
を指定して初期化できるのです。
struct T(i32, i32);
// タプル記法
let t1 = T(1, 2);
// 構造体記法
let t2 = T { 0: 1, 1: 2 };
// どちらも格納されるデータは同じ
assert_eq!(t1.0, t2.0);
assert_eq!(t1.1, t2.1);
これなら要素を取り出すのに配列のようなアクセスではなく、構造体のようなアクセスなのも理解できると思います。
同様に「タプルの要素をイテレートして取り出したい」という要望も聞きますが、これも無理な相談だと分かるかと思います。
因みに思想関係なく今からRustでタプルに []
記法を使ったりタプルをイテレータとして扱ったりできるかというと、できません。
Rustの []
やイテレータはそれぞれトレイトで定義されており返り値の型が同一でないといけません。それぞれ異なる型が入るかもしれないタプルにそれらのトレイトは実装できません。
タプルとユニット
タプルの話が出たのでユニット型にも触れておきます。 ユニット型は0要素のタプルです。ML系言語だと0要素のレコードでもあります。 これもSMLだと分かりやすいです。
$ sml
Standard ML of New Jersey v110.79 [built: Tue Aug 8 23:21:20 2017]
(* 0要素のレコードを作る *)
- {};
(* 0要素のタプル、unitと区別されていない *)
val it = () : unit
Rustだと陽には表れなくて、コンパイラのコードを読んで初めて分かるので意外に思った方もいると思います。
蛇足: 1要素タプル
Rustには1要素タプルがありますが、ML系言語には1要素タプルがありません。
$ ocaml
OCaml version 4.05.0
# (1,);;
Error: Syntax error: operator expected.
特にSMLは {1 = ...}
だけタプルにならず、上で示した通り {}
や {1 = ..., 2 = ...}
だとタプルができます。不思議ですね。
$ sml
Standard ML of New Jersey v110.79 [built: Tue Aug 8 23:21:20 2017]
- (1,);;
= ;
stdIn:1.4-1.7 Error: syntax error: deleting COMMA RPAREN SEMICOLON
- { 1 = 1};;
val it = {1=1} : {1:int}
-
まとめ
- タプルは特殊な構造体であり、ドット記法でフィールドにアクセスするのは妥当
- ユニット型は0要素タプル