コンテンツにスキップ

Rustのタプル

タプル(tuple)は、いくつかの値をひとつにまとめたものです。まとめる値は、それぞれ違う型でもかまいません。わざわざ新しい型を定義しなくても、関係のある値をさっと束ねられる便利な道具です。

fn main() {
let person: (&str, i32, bool) = ("Alice", 30, true);
println!("{:?}", person);
}

実行結果:

("Alice", 30, true)
▶ 実際に試してみよう

このタプルには &str、i32、bool という3つの違う型が入っていますね。これは配列にはできないことです。配列の要素は、すべて同じ型でなければならないからです。

タプル全体を表示するには、{} ではなく {:?}(デバッグ表示)を使います。ふつうの {} はタプルには使えません。

.0、.1、.2 のように書きます。タプルの番号は0から始まります。

fn main() {
let person = ("Alice", 30, true);
println!("名前: {}", person.0);
println!("年齢: {}", person.1);
println!("有効: {}", person.2);
}

実行結果:

名前: Alice
年齢: 30
有効: true
▶ 実際に試してみよう

位置は、コードに直接書いた数字でなければなりません。person.i のように変数は使えません。位置ごとに違う型が入っているかもしれず、Rustはコンパイルの時点で型を知る必要があるからです。

すべての値を、1行でそれぞれ別の変数に取り出せます。これを分解(destructuring)と呼びます。

fn main() {
let person = ("Alice", 30, true);
let (name, age, is_active) = person;
println!("{name}は{age}歳、有効: {is_active}");
}

実行結果:

Aliceは30歳、有効: true
▶ 実際に試してみよう

.0、.1、.2 を何度も書くより、このほうが読みやすいことがよくあります。要素が3つを超えるようなタプルでは、特にそうです。

関数が宣言できる戻り値の型はひとつだけです。そこで、複数の値を返したいときはタプルを使うのが自然です。

fn min_max(numbers: &[i32]) -> (i32, i32) {
let min = *numbers.iter().min().unwrap();
let max = *numbers.iter().max().unwrap();
(min, max)
}
fn main() {
let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]);
println!("最小: {smallest}、最大: {largest}");
}

実行結果:

最小: 1、最大: 9
▶ 実際に試してみよう

&[i32]、.iter()、.unwrap() は、今は気にしなくて大丈夫です。それぞれスライス、イテレータ、Optionのページで扱います。ここで注目してほしいのは、戻り値の型 (i32, i32) と、main がそれをすぐに2つの変数に分解しているところです。

タプルを変更する・値を入れ替える

Section titled “タプルを変更する・値を入れ替える”

タプルを mut で宣言すれば、同じ .0、.1 の位置を使って、要素を1つずつ変更できます。変えられるのは値だけで、形は変えられません。タプルの要素の数と型は、最初に作ったときのまま変わりません。

分解は逆向きにも使えます。つまり、すでにある変数に向けて代入できるのです。これを使うと、一時的な変数なしで、2つの値を1行で入れ替えられます。一部の要素だけが欲しいときは、いらない要素の位置に _ と書きます。

fn main() {
let mut point = (3, 4);
point.0 += 10; // 1つの要素をその場で変更する
println!("point は {:?}", point);
let mut a = 1;
let mut b = 2;
(a, b) = (b, a); // 2つの値を入れ替える
println!("a = {a}, b = {b}");
let (x, _) = point; // x は残し、2つ目の要素は無視する
println!("x = {x}");
}

実行結果:

point は (13, 4)
a = 2, b = 1
x = 13
▶ 実際に試してみよう

入れ替えがうまくいくのは、Rustが先に右辺の (b, a) を作るからです。そのあとで初めて、それを a と b に分解します。_ やパターンを使ってできることは、パターンマッチングのページでもっとたくさん紹介しています。

実は、あなたはもう気づかないうちにタプルに出会っています。() はユニット型(unit type)と呼ばれ、意味のある値を何も返さない関数が返すものです。fn main() の戻り値の型も、書かれていないだけで実は () です。

タプルは、名前をつけるほどでもないちょっとしたまとまりにぴったりです。特に、関数から2つか3つの値を返すときに活躍します。一方、値のまとまりが「名前と年齢を持つ Person」のように、コードのあちこちで名前で参照するはっきりした概念を表すなら、構造体のほうが読みやすく、たいていはよい選択です。

2つの数の和と積を両方返す関数 sum_and_product(a: i32, b: i32) -> (i32, i32) を書いてください。main で5と6を渡して呼び出し、結果を分解しましょう。ヒント:関数の最後の行は、タプルそのもの (a + b, a * b) です。セミコロンは付けません。

解答を見る
fn sum_and_product(a: i32, b: i32) -> (i32, i32) {
(a + b, a * b)
}
fn main() {
let (sum, product) = sum_and_product(5, 6);
println!("和 = {sum}、積 = {product}");
}

実行結果:

和 = 11、積 = 30
▶ 実際に試してみよう

長方形は、2つの点のタプルとして表せます。それぞれの点もタプルです:let rect = ((1, 2), (5, 5));。この長方形の幅、高さ、面積を求めてください。ヒント:タプルは入れ子にできます。rect.1.0 は「2つ目の点の1つ目の要素」という意味です。

解答を見る
fn main() {
let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0;
let height = rect.1.1 - rect.0.1;
println!("幅{width}、高さ{height}、面積{}", width * height);
}

実行結果:

幅4、高さ3、面積12
▶ 実際に試してみよう

2つの点を一度に分解することもできます:let ((x1, y1), (x2, y2)) = rect;。書く量は増えますが、rect.1.0 - rect.0.0 より x2 - x1 のほうが読みやすいですね。こんなふうにドットが積み重なってきたら、名前付きのフィールドを持つ構造体にしたほうがわかりやすい、というサインです。

  • タプルは、違う型の値もまとめられます: (name, age, is_active)。
  • 要素は .0、.1、.2 で取り出すか、分解で一度に取り出します。
  • 関数から複数の値を返すには、タプルを使うのが自然です。
  • mut のタプルは要素を変更できますが、要素の数と型は変えられません。(a, b) = (b, a); で2つの値を入れ替えられます。
  • 名前つきで繰り返し使うまとまりには、構造体を使いましょう。

理解度チェック

1. 配列にはできなくて、タプルにはできることは何ですか?

2. person というタプルの2番目の要素を取り出すには、どう書きますか?

3. let mut a = 1; let mut b = 2; (a, b) = (b, a); の後、a と b はいくつですか?

スコア: 0 / 3