この記事では、Rustのデータ型(整数)について、プログラミング初心者の方にも分かりやすく解説を進めていきます。
Rustを学び始めると、i32
やu64
といった見慣れない型がたくさん出てきて、「うわ、なんか難しそう…」と感じるかもしれませんね。でも大丈夫!
この記事を読むことで、以下の点がスッキリ理解できるようになります。
- Rustにはどんな種類の整数があるの?
- 符号付き?符号なし?サイズ?どう違うの?
- どうやってコードに書けばいいの?
- 整数を使った簡単な計算方法は?
- Rustならではの注意点って何?
目次[非表示]
Rustのデータ型における「整数」とは?
プログラミングをしていると、必ずと言っていいほど登場するのが「数値」ですよね。
中でも「整数」は、個数や回数、点数など、小数点を含まないカチッとした数を表すのに使われます。
Rustの世界では、この整数を扱うときに、「どのくらいの大きさの数値を扱う可能性があるか」「マイナスの数も扱うか」をあらかじめ決めておく必要があります。
これは、コンピュータの中で数値を記憶しておくための「箱(メモリ)」の大きさを、無駄なく、かつ安全に使うためのRust流の考え方なんです。
他のプログラミング言語と比べると、型の種類が多くて少し厳密に見えるかもしれませんが、慣れてしまえば、メモリの節約になったり、予期せぬエラーを防いだりするのに役立ちます。
まずは「Rustは整数に真面目なんだな」くらいに捉えて、気楽に読み進めてくださいね。
Rustの整数型の種類を知ろう!符号とサイズが鍵
Rustの整数型は、大きく分けて「符号付き」と「符号なし」の2つのグループがあり、さらにそれぞれに「サイズ(ビット数)」の違いがあります。なんだか難しそうに聞こえますが、ポイントを押さえれば簡単です!
基本的には、型名の最初の文字がi
なら「符号付き(マイナスもOK)」、u
なら「符号なし(0以上のみ)」を表します。そして、その後に続く数字が「ビット数(サイズの大きさ)」を示しています。
主な整数型をリストアップしてみましょう。
- 符号付き整数:
i8
,i16
,i32
,i64
,i128
- 符号なし整数:
u8
,u16
,u32
,u64
,u128
- 環境依存の整数:
isize
,usize
例えばi32
なら、「符号付き(i)の32ビット(32)サイズの整数」という意味になります。
ビット数が大きいほど、より大きな(またはより小さな)数値を扱うことができますが、その分メモリも多く使います。それでは、それぞれのグループをもう少し詳しく見ていきましょう。
符号付き整数 (i8, i16, i32, i64, i128)
「符号付き」という名前の通り、プラスの値、マイナスの値、そしてゼロを扱うことができる整数型です。i
は「integer(整数)」の頭文字と考えると覚えやすいかもしれませんね(実際は signed integer ですが)。
i8
: -128 から 127 までi16
: -32,768 から 32,767 までi32
: 約-21億 から 約21億 までi64
: とてつもなく大きな範囲(約-922京 から 約922京)i128
: さらにとてつもなく大きな範囲
日常的な計算や、特に指定がない場合は、i32
がよく使われます。Rustコンパイラも、型を明示しない場合は整数をi32
と推測することが多いです。
温度や座標のように、マイナスになる可能性がある数値を扱う場合に便利です。
符号なし整数 (u8, u16, u32, u64, u128)
こちらは「符号なし」なので、ゼロとプラスの値のみを扱います。
マイナスの値になることが絶対にない、と分かっている場合に使う型です。u
は「unsigned(符号なし)」の頭文字です。
u8
: 0 から 255 までu16
: 0 から 65,535 までu32
: 0 から 約42億 までu64
: 0 から とてつもなく大きな数(約1844京)u128
: 0 から さらにとてつもなく大きな数
例えば、物の個数、ループの回数、ユーザーIDなど、絶対にマイナスにならない数値を扱う際に適しています。u8
は、バイトデータを扱うときにもよく登場しますね。
環境依存の整数 (isize / usize)
isize
とusize
は少し特殊な整数型です。これらは、プログラムを実行するコンピュータのCPUが32ビットか64ビットかによって、サイズ(扱える範囲)が変わります。
- 32ビット環境:
isize
はi32
相当、usize
はu32
相当 - 64ビット環境:
isize
はi64
相当、usize
はu64
相当
これらは主に、配列やベクタといった複数のデータをまとめたもの(コレクション)の要素を指し示す「添え字(インデックス)」や、メモリのサイズを表すのに使われます。
特にusize
はコレクションのインデックスとして非常によく使われるので覚えておくと良いでしょう。
Rustでの整数リテラルの書き方
ソースコードの中に直接数値を書く方法(リテラル)も見ておきましょう。Rustでは、数値を読みやすくするための便利な書き方が用意されています。
fn main() { // 普通の10進数 let decimal = 98_222; println!("10進数: {}", decimal); // 16進数 (0xから始める) let hex = 0xff; println!("16進数: {}", hex); // 255と表示される // 8進数 (0oから始める) let octal = 0o77; println!("8進数: {}", octal); // 63と表示される // 2進数 (0bから始める) let binary = 0b1111_0000; println!("2進数: {}", binary); // 240と表示される // バイト (u8のみ) let byte = b'A'; println!("バイト: {}", byte); // 65と表示される (ASCIIコード) // 型を明示するサフィックス let number_u32: u32 = 5_000u32; // u32型を指定 let number_i64 = 100i64; // i64型を指定 println!("u32: {}, i64: {}", number_u32, number_i64); // _ で区切って読みやすく let large_number = 1_000_000; println!("大きな数: {}", large_number); }
表示結果
10進数: 98222 16進数: 255 8進数: 63 2進数: 240 バイト: 65 u32: 5000, i64: 100 大きな数: 1000000
このように、アンダースコア_
を桁区切り文字として使えるのが地味に便利です。
大きな数値も格段に読みやすくなりますね。また、数値の後ろにu32
やi64
のように型名を付ける(サフィックス)ことで、その数値がどの型であるかを明示的に指定できます。
Rustの整数型の使い方(基本操作とサンプル)
整数型を宣言したら、次は実際に計算などで使ってみましょう。
基本的な算術演算(足し算、引き算、掛け算、割り算、剰余)は、他の多くのプログラミング言語と同じように行えます。
fn main() { let a = 10; // 型を明示しない場合、i32になることが多い let b: i32 = 5; // 明示的にi32を指定 // 足し算 let sum = a + b; println!("{} + {} = {}", a, b, sum); // 引き算 let difference = a - b; println!("{} - {} = {}", a, b, difference); // 掛け算 let product = a * b; println!("{} * {} = {}", a, b, product); // 割り算 (整数同士の割り算は、小数点以下が切り捨てられる) let quotient = a / b; println!("{} / {} = {}", a, b, quotient); // 剰余 (割り算の余り) let remainder = a % b; println!("{} % {} = {}", a, b, remainder); // 10割る5は割り切れるので余り0 let c = 11; let remainder2 = c % b; println!("{} % {} = {}", c, b, remainder2); // 11割る5は余り1 }
表示結果
10 + 5 = 15 10 - 5 = 5 10 * 5 = 50 10 / 5 = 2 10 % 5 = 0 11 % 5 = 1
注意点として、整数同士の割り算では、結果も整数になります。
つまり、割り切れない場合は小数点以下が切り捨てられます(例: 5 / 2
は 2.5
ではなく 2
になります)。
型推論と明示的な型指定
先ほどの例で let a = 10;
のように書くと、Rustコンパイラは「ふむふむ、これは整数だな。特に指定がないから、とりあえずi32
にしておこう」という感じで、自動的に型を決めてくれることがあります。これを型推論と呼びます。
一方で、let b: i32 = 5;
のように、コロン:
の後ろに型名を書くことで、「この変数b
は絶対にi32
型ですよ!」と明示的に指定することもできます。
どちらを使うかは状況によりますが、
- どの型を使っているか一目で分かるようにしたい場合
- コンパイラのデフォルト(例えば
i32
)とは違う型を使いたい場合(例:u8
やi64
)
には、明示的に型を指定するのがおすすめです。コードの意図が明確になり、後から読み返したときや他の人が読んだときにも分かりやすくなります。
整数を使った簡単な計算
では、少しだけ具体的な計算をしてみましょう。
ここでは、商品の単価(u32
型)と個数(u8
型)から合計金額を計算する例を考えます。ただし、型の違う整数同士を直接計算することはできないため、型を合わせる(キャストする)必要があります。
fn main() { let unit_price: u32 = 1500; // 商品単価 (0以上の値なのでu32) let quantity: u8 = 3; // 個数 (多くても255個までと想定しu8) // 合計金額を計算したい // そのまま計算しようとするとエラーになる (型が違うため) // let total_price = unit_price * quantity; // これはエラー! // quantityをu32型に変換してから計算する let total_price = unit_price * (quantity as u32); // ↑ (quantity as u32) で u8型 を u32型 に変換! println!("単価: {} 円", unit_price); println!("個数: {} 個", quantity); println!("合計金額: {} 円", total_price); // 参考: 型を合わせないとどうなるか? // 下の行のコメントを外して実行(cargo run)すると、 // mismatched types というエラーメッセージが表示されるはずです。 // println!("{}", unit_price * quantity); }
表示結果
単価: 1500 円 個数: 3 個 合計金額: 4500 円
この例のポイントは、quantity as u32
の部分です。u8
型の変数quantity
を、unit_price
と同じu32
型に一時的に「変身」させてから掛け算を行っています。
このようにas
キーワードを使うことで、ある数値型を別の数値型へ変換(キャスト)できます。Rustでは型の扱いに厳密なので、異なる型の整数同士で計算する場合は、このように型を揃える作業が必要になることが多いです。
ここは注意!Rustの整数型とオーバーフロー
Rustで整数を扱う上で、絶対に知っておいてほしいのが「オーバーフロー」です。オーバーフローとは、変数の型が表現できる値の範囲を超えた計算結果になった場合に発生する現象のことです。
例えば、u8
型は0から255までの値しか扱えません。もし、u8
型の変数に255 + 1
のような計算をさせようとすると、256という値はu8
の範囲を超えてしまいます。これがオーバーフローです。
Rustでは、オーバーフローが起きたときの挙動が、プログラムのビルド方法(DebugビルドかReleaseビルドか)によって異なります。
- Debugビルド(開発中によく使うモード)
オーバーフローが発生すると、プログラムはパニックを起こして即座に停止します。これにより、開発者は問題にすぐに気づくことができます。 - Releaseビルド(配布用によく使うモード)
オーバーフローが発生してもプログラムは停止せず、値が型の範囲の反対側に「ラップアラウンド」します。例えばu8
型で255 + 1
を計算すると0
に、0 - 1
を計算すると255
になります。(これは2の補数表現と呼ばれるコンピュータの数値表現方法に基づいています)
Releaseビルドで勝手に値が変わってしまう(ラップアラウンド)のは、予期せぬバグの原因になる可能性があり危険です。
そのため、オーバーフローが起こる可能性のある計算については、開発者が意識して対処する必要があります。
オーバーフローへの対処法
幸いなことに、Rustにはオーバーフローを安全に扱うための便利なメソッドが用意されています。
-
checked_*
メソッドchecked_add()
,checked_sub()
,checked_mul()
,checked_div()
などがあります。
これらは計算結果がオーバーフローしない場合はSome(結果)
を、オーバーフローする場合はNone
を返します。計算が成功したかどうかを安全に確認できるため、オーバーフローを絶対に避けたい場合に最も推奨される方法です。ソースコード例:
fn main() { let x: u8 = 255; let y: u8 = 1; let sum_checked = x.checked_add(y); // 255 + 1 をチェック付きで計算 match sum_checked { Some(result) => println!("計算成功: {}", result), None => println!("おっと!オーバーフローしました!"), } let z: u8 = 100; let sum_ok = z.checked_add(y); // 100 + 1 はOK if let Some(result) = sum_ok { println!("計算成功: {}", result); } }
表示結果例
おっと!オーバーフローしました! 計算成功: 101
-
wrapping_*
メソッドwrapping_add()
,wrapping_sub()
など。
これらは、ビルドモードに関わらず、常にラップアラウンド(値が範囲を超えたら反対側から現れる)する計算を行います。オーバーフローが起きても問題なく、ラップアラウンドする挙動を意図的に利用したい場合に用います。 -
saturating_*
メソッドsaturating_add()
,saturating_sub()
など。
計算結果が型の最大値や最小値を超えた場合、その最大値または最小値で「飽和」させます。つまり、限界値に張り付いたままそれ以上(以下)にはなりません。例えば、ゲームのスコアやパラメータのように、上限値や下限値を設けたい場合に便利です。
どの方法を使うかは、プログラムの要件によって決まりますが、意図しないオーバーフローはバグの元です。特にユーザーからの入力や外部データを扱う際は、checked_*
メソッドなどを使って、オーバーフローの可能性を常に意識することが、安全なRustプログラムを書く上でとても大事になります。
【まとめ】Rustの整数型を理解して安全なコードを書こう
今回は、Rustのデータ型の中でも基本となる「整数型」について、その種類、書き方、使い方、そして大事な注意点であるオーバーフローまでを見てきました。
ポイントを振り返ってみましょう。
- Rustには符号(プラスマイナスあり/なし)とサイズ(ビット数)によって多くの整数型がある(
i32
,u8
,usize
など)。 - 状況に応じて適切な型を選ぶことが、メモリ効率や安全性につながる。
- 数値リテラルは
_
で区切ったり、型サフィックスを付けたりできる。 - 基本的な算術演算が可能だが、違う型同士の計算には型キャスト(
as
)が必要な場合がある。 - 整数オーバーフローはRustで特に注意すべき点で、
checked_*
メソッドなどで安全に対処できる。
最初は型の多さに戸惑うかもしれませんが、それぞれの型に意味があり、Rustがいかに安全性や効率性を考えて設計されているかが分かってくると、だんだん面白くなってくるはずです。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。