タプルとユニット
Moveは、タプルを完全にはサポートしていません。タプルをファーストクラスの値として扱う他の言語から来た人は期待するかもしれませんが。ただし、複数の戻り値をサポートするため、Moveにはタプルのような式があります。これらの式は実行時、具体的な値とはなりません (バイトコードにはタプルはありません)。そのため、非常に限定的です。式(通常は関数の戻り位置)へのみ出現出来、ローカル変数にバインドできず、構造体に保存できず、タプル型を使用してジェネリクスのインスタンス化は出来ません。
同様に、unit ()
は、式ベースとなる様Moveソース言語が作成した型です。ユニット値()
は実行事の値とはなりません。unit()
は空のタプルと見なすことが出来、タプルに適用される制限はユニットにも適用されます。
これらの 制限を考えると、言語にタプルが存在するのは違和感が有るかもしれません。しかし、他の言語でのタプルの最も一般的なユースケースのひとつは、関数が複数の値を返す事を許可する機能です。ある言語では、構造体が複数の戻り値を含む記述をユーザーに強制することでこれを回避しています。ただしMoveでは、構造体内へ参照を配置出来ません。そのため、Moveは複数の戻り値をサポートする必要が有ります。これらの複数の戻り値は全て、バイトコードレベルでスタックへプッシュされます。ソースレベルでは、これらの複数の戻り値はタプルを使用して表されます。
リテラル
タプルは、括弧内で式のリストをカンマで区切って作成します。
構文 | 型 説明 | |
---|---|---|
() | (): () | ユニット、空のタプル、またはアリティ 0 のタプル |
(e1, ..., en) | e_i: Ti s.t. が0 < i <= n またはn > 0 の場合(e1, ..., en): (T1, ..., Tn) | -タプルn 、アリティn のタプル、要素n のタプル |
注意: (e)
には(e): (t)
型が有りません。言い換えると、要素がひとつしかないタプルは存在しません。括弧内に要素がひとつしかない場合、括弧は曖昧さ回避のため使用される以外、他の特別な意味は有りません。
2 つの要素を持つタプルは「ペア」と呼ばれ、3つの要素を持つタプルは「トリプル」と呼ばれる事が有ります。
例
module 0x42::example {
// これら3つの関数は全て同等です
// 戻り値の型が指定されていない場合は`()`とみなされます
fun returns_unit_1() { }
// 空の式ブロック内で暗黙の()値が有ります
fun returns_unit_2(): () { }
// `returns_unit_1`と`returns_unit_2`の明白なバージョン
fun returns_unit_3(): () { () }
fun returns_3_values(): (u64, bool, address) {
(0, false, @0x42)
}
fun returns_4_values(x: &u64): (&u64, u8, u128, vector<u8>) {
(x, 0, 1, b"foobar")
}
}
操作
現在、タプルに対して実行できる唯一の操作は、構造化解除です。
構造化解除(Destructuring)
任意のサイズのタプルは、let
バインディングまたは割り当てのいずれかで構造化解除できます。
例えば:
module 0x42::example {
// これら3つの関数は全て同等です
fun returns_unit() {}
fun returns_2_values(): (bool, bool) { (true, false) }
fun returns_4_values(x: &u64): (&u64, u8, u128, vector<u8>) { (x, 0, 1, b"foobar") }
fun examples(cond: bool) {
let () = ();
let (x, y): (u8, u64) = (0, 1);
let (a, b, c, d) = (@0x0, 0, false, b"");
() = ();
(x, y) = if (cond) (1, 2) else (3, 4);
(a, b, c, d) = (@0x1, 1, true, b"1");
}
fun examples_with_function_calls() {
let () = returns_unit();
let (x, y): (bool, bool) = returns_2_values();
let (a, b, c, d) = returns_4_values(&0);
() = returns_unit();
(x, y) = returns_2_values();
(a, b, c, d) = returns_4_values(&1);
}
}
詳細はMove変数を御覧下さい。
サブタイプ
参照とともに、タプルはMoveでサブタイプ化される唯一の他の型です。タプルのサブタイプ化は、参照でサブタイプ化されるという意味でのみ行われます(共変的な方法で)。
例えば:
script {
fun example() {
let x: &u64 = &0;
let y: &mut u64 = &mut 1;
// (&u64, &mut u64)は(&u64, &u64)のサブタイプです
// &mut u64は&u64のサブタイプであるため
let (a, b): (&u64, &u64) = (x, y);
// (&mut u64, &mut u64)は(&u64, &u64)のサブタイプです
// &mut u64は&u64のサブタイプであるため
let (c, d): (&u64, &u64) = (y, y);
// エラー! (&u64, &mut u64)は(&mut u64, &mut u64)のサブタイプでは有りません
// &u64は&mut u64のサブタイプではないため
let (e, f): (&mut u64, &mut u64) = (x, y);
}
}
所有権
上記の通り、タプルの値は実行時は実際存在しません。そのため、現在タプルをローカル変数へ格納出来ません(ただし、この機能はすぐ提供される可能性が有ります)。そのため、タプルをコピーする場合、まずローカル変数へ格納する必要があるため、現在はタプルを移動する事以外出来ません。