はじめに
データ型へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習できます。
この実験では、Rust におけるデータ型の概念を探ります。すべての値には、それがどのように扱われるかを決定するための特定の型が割り当てられており、複数の型が考えられる場合には、コンパイラに必要な情報を提供するために型アノテーションを追加する必要があります。
This tutorial is from open-source community. Access the source code
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
データ型へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習できます。
この実験では、Rust におけるデータ型の概念を探ります。すべての値には、それがどのように扱われるかを決定するための特定の型が割り当てられており、複数の型が考えられる場合には、コンパイラに必要な情報を提供するために型アノテーションを追加する必要があります。
Rust におけるすべての値は、特定のデータ型に属しており、それがどの種類のデータであるかを Rust に伝えることで、そのデータとどのようにやりとりするかを知ることができます。ここでは、2 つのデータ型のサブセット:スカラー型と複合型を見ていきます。
Rust は静的型付け言語であることを忘れないでください。これは、コンパイル時にすべての変数の型を知る必要があることを意味します。コンパイラは通常、値とその使い方に基づいて、どの型を使用したいかを推論することができます。たとえば、「推測値と秘密の数字を比較する」でparse
を使ってString
を数値型に変換した場合のように、複数の型が考えられる場合には、次のように型アノテーションを追加する必要があります。
let guess: u32 = "42".parse().expect("Not a number!");
前のコードで示した: u32
の型アノテーションを追加しない場合、Rust は次のエラーを表示します。これは、コンパイラがどの型を使用したいかを知るために、私たちからさらに情報を必要としていることを意味します。
$ cargo build
Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
error[E0282]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^ consider giving `guess` a type
他のデータ型については、異なる型アノテーションが見られます。
スカラー型は単一の値を表します。Rust には 4 つの主なスカラー型があります。整数型、浮動小数点数型、ブール型、文字型。他のプログラミング言語からこれらを知っている人もいるでしょう。Rust におけるこれらの型の使い方を見ていきましょう。
整数とは、小数部分を持たない数値のことです。第 2 章では、u32
型という整数型を使用しました。この型宣言は、関連付けられた値が符号なし整数(符号付き整数型はu
ではなくi
で始まります)であり、32 ビットの空間を占める必要があることを示しています。表 3-1 は、Rust の組み込み整数型を示しています。これらのバリアントのいずれかを使用して、整数値の型を宣言できます。
表 3-1:Rust の整数型
長さ 符号付き 符号なし
8 ビット i8
u8
16 ビット i16
u16
32 ビット i32
u32
64 ビット i64
u64
128 ビット i128
u128
arch isize
usize
各バリアントは、符号付きまたは符号なしのいずれかであり、明示的なサイズを持っています。符号付きと符号なしは、数値が負になる可能性があるかどうか、つまり、数値に符号が必要かどうか(符号付き)または常に正であり、符号なしで表現できるかどうか(符号なし)を指します。これは、紙に数字を書くようなものです。符号が重要な場合は、数字はプラス記号またはマイナス記号で示されます。ただし、数字が正であると安全に仮定できる場合は、符号なしで表示されます。符号付き数値は、2 の補数表現を使用して格納されます。
各符号付きバリアントは、-(2^(n-1)) から 2^(n-1) - 1(両端を含む)までの数値を格納できます。ここで、nは、そのバリアントが使用するビット数です。したがって、i8
は-(2^7) から 2^7 - 1、つまり -128 から 127 までの数値を格納できます。符号なしバリアントは、0 から 2^n - 1 までの数値を格納できます。したがって、u8
は 0 から 2^8 - 1、つまり 0 から 255 までの数値を格納できます。
さらに、isize
型とusize
型は、プログラムが実行されているコンピューターのアーキテクチャに依存します。これは、表で「arch」として示されています。64 ビットアーキテクチャの場合は 64 ビット、32 ビットアーキテクチャの場合は 32 ビットです。
整数リテラルは、表 3-2 に示す形式のいずれかで記述できます。複数の数値型が可能な数値リテラルは、型を指定するための57u8
のような型サフィックスを使用できることに注意してください。数値リテラルは、1_000
のように、数値を読みやすくするために視覚的な区切り文字として_
を使用することもできます。これは、1000
を指定した場合と同じ値になります。
表 3-2:Rust の整数リテラル
数値リテラル 例
10 進数 98_222
16 進数 0xff
8 進数 0o77
2 進数 0b1111_0000
バイト(u8
のみ) b'A'
では、どの整数型を使用すればよいのでしょうか?わからない場合は、Rust のデフォルトが一般的に良い出発点です。整数型はデフォルトでi32
になります。isize
またはusize
を使用する主な状況は、何らかのコレクションにインデックスを付ける場合です。
整数のオーバーフロー
u8
型の変数があり、0 から 255 までの値を保持できるとします。変数をその範囲外の値(256 など)に変更しようとすると、整数のオーバーフローが発生し、2 つの動作のいずれかが発生する可能性があります。デバッグモードでコンパイルする場合、Rust は整数のオーバーフローのチェックを含み、この動作が発生した場合にプログラムが実行時にパニックを起こす原因となります。Rust は、プログラムがエラーで終了する場合にパニックという用語を使用します。「Unrecoverable Errors with panic!」でパニックについて詳しく説明します。
--release
フラグを使用してリリースモードでコンパイルする場合、Rust はパニックを引き起こす整数のオーバーフローのチェックを含みません。代わりに、オーバーフローが発生した場合、Rust は2 の補数ラップを実行します。簡単に言うと、型が保持できる最大値を超える値は、型が保持できる最小値に「ラップアラウンド」します。u8
の場合、値 256 は 0 になり、値 257 は 1 になります。プログラムはパニックを起こしませんが、変数は、おそらく期待していたものではない値を持つことになります。整数のオーバーフローのラップ動作に依存することは、エラーと見なされます。オーバーフローの可能性を明示的に処理するには、プリミティブ数値型に対して標準ライブラリが提供するこれらのメソッドファミリーを使用できます。
wrapping_*
メソッド(wrapping_add
など)を使用して、すべてのモードでラップします。checked_*
メソッドを使用して、オーバーフローがある場合はNone
値を返します。overflowing_*
メソッドを使用して、値とオーバーフローがあったかどうかを示すブール値を返します。saturating_*
メソッドを使用して、値の最小値または最大値で飽和させます。
Rust には、小数点を持つ数である「浮動小数点数」用の 2 つのプリミティブ型もあります。Rust の浮動小数点数型は、それぞれ 32 ビットのf32
と 64 ビットのf64
です。デフォルトの型はf64
です。なぜなら、現代の CPU では、f32
とほぼ同じ速度で動作しますが、より高い精度を持つことができるからです。すべての浮動小数点数型は符号付きです。
data-types
という名前の新しいプロジェクトを作成しましょう。
cargo new data-types
cd data-types
以下は、浮動小数点数を使ったサンプルです。
ファイル名:src/main.rs
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
浮動小数点数は IEEE-754 標準に従って表されます。f32
型は単精度浮動小数点数で、f64
は倍精度浮動小数点数です。
Rust は、すべての数値型に対して期待される基本的な数学演算をサポートしています。加算、減算、乗算、除算、余り。整数の除算は、0 に向かって最も近い整数に切り捨てます。次のコードは、let
文で各数値演算をどのように使用するかを示しています。
ファイル名:src/main.rs
fn main() {
// 加算
let sum = 5 + 10;
// 減算
let difference = 95.5 - 4.3;
// 乗算
let product = 4 * 30;
// 除算
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // 結果は -1 になります
// 余り
let remainder = 43 % 5;
}
これらの文の各式は、数学演算子を使用しており、単一の値に評価され、その後変数に束縛されます。付録 B には、Rust が提供するすべての演算子の一覧があります。
他の多くのプログラミング言語と同様に、Rust のブール型は 2 つの値のみを持ちます。true
とfalse
です。ブール型は 1 バイトのサイズです。Rust のブール型はbool
を使用して指定します。たとえば:
ファイル名:src/main.rs
fn main() {
let t = true;
let f: bool = false; // 明示的な型注釈付き
}
ブール値を使用する主な方法は、if
式などの条件分岐を通じることです。「制御フロー」では、Rust におけるif
式の動作方法について説明します。
Rust のchar
型は、その言語の最も基本的なアルファベット型です。以下は、char
値を宣言するいくつかの例です。
ファイル名:src/main.rs
fn main() {
let c = 'z';
let z: char = 'ℤ'; // 明示的な型注釈付き
let heart_eyed_cat = '😻';
}
文字列リテラルではダブルクォートを使用するのに対し、char
リテラルはシングルクォートで指定することに注意してください。Rust のchar
型は 4 バイトのサイズで、Unicode スカラ値を表します。これは、ASCII だけでなくはるかに多くのものを表すことができることを意味します。アクセント付き文字、中国語、日本語、韓国語の文字、絵文字、ゼロ幅スペースはすべて、Rust では有効なchar
値です。Unicode スカラ値は、U+0000
からU+D7FF
およびU+E000
からU+10FFFF
の範囲(両端を含む)です。ただし、「文字」は Unicode においては実際には概念ではないため、「文字」が何であるかに関する人間の直感が、Rust におけるchar
と一致しない場合があります。「文字列で UTF-8 エンコードされた文字列を格納する」でこのトピックについて詳細に説明します。
「複合型」は、複数の値を 1 つの型にまとめることができます。Rust には 2 つのプリミティブな複合型があります。タプルと配列です。
タプルは、さまざまな型の複数の値を 1 つの複合型にまとめる一般的な方法です。タプルは固定長です。宣言すると、サイズが増減しません。
丸括弧の中にカンマ区切りの値のリストを書くことでタプルを作成します。タプルの各位置には型があり、タプル内の異なる値の型は必ずしも同じでなくてもよいです。この例ではオプショナルな型注釈を追加しています。
ファイル名:src/main.rs
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
変数tup
は、タプル全体に束縛されます。なぜなら、タプルは単一の複合要素と見なされるからです。タプルから個々の値を取り出すには、パターンマッチングを使ってタプル値を分解することができます。例えば:
ファイル名:src/main.rs
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");
}
このプログラムはまずタプルを作成して変数tup
に束縛します。その後、let
を使ったパターンを使ってtup
を取り、3 つの別々の変数x
、y
、z
に変換します。これは「分解」と呼ばれ、単一のタプルを 3 つの部分に分割するためです。最後に、プログラムはy
の値を出力します。それは6.4
です。
また、アクセスしたい値のインデックスに続けてピリオド(.
)を使うことで、直接タプル要素にアクセスすることもできます。例えば:
ファイル名:src/main.rs
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
このプログラムはタプルx
を作成し、その後、それぞれのインデックスを使ってタプルの各要素にアクセスします。ほとんどのプログラミング言語と同様に、タプルの最初のインデックスは 0 です。
値がないタプルには特別な名前「ユニット」があります。この値とその対応する型はどちらも()
と書かれ、空の値または空の戻り型を表します。式が他の値を返さない場合、暗黙的にユニット値を返します。
複数の値のコレクションを持つ別の方法は、「配列」を使うことです。タプルとは異なり、配列の各要素は必ず同じ型でなければなりません。他の一部の言語の配列とは異なり、Rust の配列は固定長です。
配列の値は、角括弧の中にカンマ区切りのリストとして書きます。
ファイル名:src/main.rs
fn main() {
let a = [1, 2, 3, 4, 5];
}
データをスタックに割り当てたい場合や、常に固定数の要素を持つことを保証したい場合、配列は便利です。ただし、配列はベクター型ほど柔軟ではありません。ベクターは、標準ライブラリによって提供される似たようなコレクション型で、サイズの増減が可能です。配列とベクターのどちらを使うか迷った場合は、おそらくベクターを使うべきです。第 8 章ではベクターについて詳しく説明します。
ただし、要素数が変更される必要がないことがわかっている場合、配列の方が便利です。たとえば、プログラムで月の名前を使う場合、12 個の要素が常に含まれることがわかっているため、おそらく配列を使う方が適切でしょう。
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
配列の型は、各要素の型、セミコロン、そして配列の要素数を角括弧で囲んで書きます。例えば:
let a: [i32; 5] = [1, 2, 3, 4, 5];
ここで、i32
は各要素の型です。セミコロンの後の数字5
は、配列が 5 つの要素を含むことを示しています。
また、各要素に同じ値を含む配列を初期化することもできます。方法は、初期値を指定してからセミコロン、そして角括弧で囲んだ配列の長さを指定することです。例えば:
let a = [3; 5];
a
と名付けられた配列は、最初にすべての要素が値3
に設定された5
つの要素を含みます。これはlet a = [3, 3, 3, 3, 3];
と書くのと同じですが、もっと簡潔な書き方です。
配列は、スタックに割り当てることができる既知の固定サイズの単一のメモリチャンクです。インデックスを使って配列の要素にアクセスすることができます。例えば:
ファイル名:src/main.rs
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
この例では、first
と名付けられた変数は値1
を取得します。なぜなら、それは配列のインデックス[0]
にある値だからです。second
と名付けられた変数は、配列のインデックス[1]
から値2
を取得します。
配列の末尾を超えた要素にアクセスしようとするとどうなるか見てみましょう。第 2 章の当て推測ゲームに似たコードを実行して、ユーザーから配列インデックスを取得するとします。
ファイル名:src/main.rs
use std::io;
fn main() {
let a = [1, 2, 3, 4, 5];
println!("Please enter an array index.");
let mut index = String::new();
io::stdin()
.read_line(&mut index)
.expect("Failed to read line");
let index: usize = index
.trim()
.parse()
.expect("Index entered was not a number");
let element = a[index];
println!(
"The value of the element at index {index} is: {element}"
);
}
このコードは正常にコンパイルされます。cargo run
を使ってこのコードを実行し、0
、1
、2
、3
、または4
を入力すると、プログラムは配列のそのインデックスに対応する値を表示します。代わりに配列の末尾を超えた数値を入力すると、たとえば10
を入力すると、次のような出力が表示されます。
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is
10', src/main.rs:19:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
このプログラムは、インデックス操作で無効な値を使用した時点で「実行時」エラーを起こしました。プログラムはエラーメッセージとともに終了し、最後のprintln!
文を実行しませんでした。インデックスを使って要素にアクセスしようとするとき、Rust は指定したインデックスが配列の長さ未満であることをチェックします。インデックスが長さ以上の場合、Rust はパニックになります。このチェックは実行時に行わなければなりません。特にこの場合、コンパイラは後でコードを実行するときにユーザーが何を入力するかを予測することはできません。
これは、Rust のメモリセーフティ原則が機能している例です。多くの低レベル言語では、この種のチェックは行われず、間違ったインデックスを指定すると、無効なメモリにアクセスできてしまいます。Rust は、メモリアクセスを許可せずに続行せずに即座に終了することで、この種のエラーから保護します。第 9 章では、Rust のエラーハンドリングと、パニックにならず、無効なメモリアクセスを許さない、読みやすく安全なコードを書く方法についてさらに説明します。
おめでとうございます!あなたはデータ型の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を行うことができます。