Progrust Library.

// book chapter

動かして学ぶRustプログラミング問題集

第2章 基本の型と演算.

この章では、Rustが最初から用意している基本の型と、それらを使った演算を学びます。
整数・小数・真偽値・文字といった型の使い分けから、四則演算・キャストまでを10問で身につけます。

第1章では変数に型を書かずに済ませてきましたが、Rustはすべての値の型がコンパイル時に決まる言語です。この章では型を明示的に書く場面が増えます。

進め方は第1章と同じです。各問題の冒頭に関連する辞書へのリンクを挙げているので、まずはリンク先で必要な知識を確認してから取り組んでください。

01 - 整数型

整数型プリミティブ型に関する問題です。
イベントの参加者数と冷凍庫の設定温度を、それぞれ変数に束縛して出力してください。
参加者数は負にならないので符号なしのu32を、設定温度は氷点下になるので符号ありのi32を、型注釈として付けてください。

期待する出力
参加者は42人です。
設定温度は-18度です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    // 参加者数を表す変数participantsを、u32の型注釈付きで42として宣言せよ

    // 設定温度を表す変数freezer_tempを、i32の型注釈付きで-18として宣言せよ

    println!("参加者は{}人です。", participants);
    println!("設定温度は{}度です。", freezer_temp);
}
Playgroundで開く
解答例と解説
fn main() {
    // 参加者数を表す変数participantsを、u32の型注釈付きで42として宣言せよ
    let participants: u32 = 42; 

    // 設定温度を表す変数freezer_tempを、i32の型注釈付きで-18として宣言せよ
    let freezer_temp: i32 = -18; 

    println!("参加者は{}人です。", participants);
    println!("設定温度は{}度です。", freezer_temp);
}
Playgroundで開く

変数の型を明示するときはlet 変数名: 型 = 値;と書きます。第1章で定数をconst TICKET_PRICE: u32 = 1200;と書いたのと同じ形式です。定数と違い、letの型注釈は省略できます(省略した場合はコンパイラが値から型を推論します)。

Rustの整数型は「符号あり/なし」と「ビット幅」の組み合わせで、i8i128u8u128などが用意されています。uで始まる符号なし整数は0以上の値しか扱えないため、負の値がありえない個数や人数に向いています。

型注釈を省略した整数リテラルはi32になります。第1章でlet money = 1000;と書いてきたものは、すべてi32となっています。迷ったらまずi32を選び、負の値を取らないならu32、というのが基本的な使い分けになります。

02 - 整数型の範囲

整数型に関する問題です。
次のコードは1年の日数を変数に束縛しようとしていますが、コンパイルエラーになります。エラーメッセージを読み、型注釈だけを変えてコンパイルが通るようにしてください。

期待する出力
1年は365日です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let days_in_year: u8 = 365; // この行でコンパイルエラーになる
    println!("1年は{}日です。", days_in_year);
}
Playgroundで開く
解答例と解説
fn main() {
    let days_in_year: u8 = 365; // この行でコンパイルエラーになる
    let days_in_year: u16 = 365; 
    println!("1年は{}日です。", days_in_year);
}
Playgroundで開く

エラーメッセージは「literal out of range for u8」、続けて「the literal 365 does not fit into the type u8 whose range is 0..=255」でした。つまり「365はu8の範囲0〜255に収まらない」という指摘です。

整数型はビット幅ごとに扱える値の範囲が決まっています。

扱える範囲
u8 0 〜 255
i8 -128 〜 127
u16 0 〜 65,535
i16 -32,768 〜 32,767

365を入れるにはu16以上の幅が必要です。u32i32でも当然コンパイルは通るので、そちらでも正解です。

なお、この問題のようにリテラルの値が範囲外だとコンパイル時に弾かれますが、実行時の計算結果が範囲を超えた場合(オーバーフロー)は別の挙動になります。詳しくは整数型の補足を参照してください。

03 - 浮動小数点型

浮動小数点型に関する問題です。
りんご1個の値段をf64で宣言し、3個買ったときの合計金額を出力してください。

期待する出力
りんご1個は128.5円です。
3個で385.5円です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    // りんごの単価を表す変数priceを、f64の型注釈付きで128.5として宣言せよ

    println!("りんご1個は{}円です。", price);

    // priceを3倍した合計金額をtotalに束縛せよ

    println!("3個で{}円です。", total);
}
Playgroundで開く
解答例と解説
fn main() {
    // りんごの単価を表す変数priceを、f64の型注釈付きで128.5として宣言せよ
    let price: f64 = 128.5; 

    println!("りんご1個は{}円です。", price);

    // priceを3倍した合計金額をtotalに束縛せよ
    let total = price * 3.0; 

    println!("3個で{}円です。", total);
}
Playgroundで開く

小数を扱う型はf32f64の2つで、整数型と違って符号なし版はありません。型注釈を省略した小数リテラルはf64になります。

ここでのポイントはprice * 3.03.0です。price * 3と書くとコンパイルエラーになります。Rustはf64と整数を暗黙に変換してくれないため、小数と掛け合わせる値も小数リテラルで書く必要があります。この「型が違うと演算できない」という性質は問題06で改めて扱います。

04 - 四則演算

数値演算に関する問題です。
2つの変数abについて、加算・減算・乗算・除算の結果を4行で出力してください。

期待する出力
16 + 4 = 20
16 - 4 = 12
16 × 4 = 64
16 ÷ 4 = 4
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let a = 16;
    let b = 4;

    // aとbの加算・減算・乗算・除算の結果を、数値演算に置き換えよ
    println!("{} + {} = {}", a, b, 20);
    println!("{} - {} = {}", a, b, 12);
    println!("{} × {} = {}", a, b, 64);
    println!("{} ÷ {} = {}", a, b, 4);
}
Playgroundで開く
解答例と解説
fn main() {
    let a = 16;
    let b = 4;

    // aとbの加算・減算・乗算・除算の結果を、数値演算に置き換えよ
    println!("{} + {} = {}", a, b, 20); 
    println!("{} - {} = {}", a, b, 12); 
    println!("{} × {} = {}", a, b, 64); 
    println!("{} ÷ {} = {}", a, b, 4); 
    println!("{} + {} = {}", a, b, a + b); 
    println!("{} - {} = {}", a, b, a - b); 
    println!("{} × {} = {}", a, b, a * b); 
    println!("{} ÷ {} = {}", a, b, a / b); 
}
Playgroundで開く

四則演算の演算子は+(加算)・-(減算)・*(乗算)・/(除算)です。多くの言語と同じ書き方で、*×ではなくアスタリスク、/÷ではなくスラッシュを使います。

println!のプレースホルダには変数だけでなくa + bのようなを直接書けます。式は評価されてから埋め込まれるため、計算結果を一度変数に束縛する必要はありません。

なお、Rustには++--(インクリメント・デクリメント)はありません。1増やしたいときはa += 1と書きます。

05 - 整数の除算と剰余

数値演算に関する問題です。
27人の生徒を1班4人ずつに分けます。できあがる班の数と、どの班にも入りきらずに余る人数を求めてください。

期待する出力
4人ずつの班が6班でき、3人余ります。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let students = 27;
    let per_group = 4;

    // できあがる班の数をgroupsに束縛せよ

    // 余る人数をremainderに束縛せよ

    println!(
        "{}人ずつの班が{}班でき、{}人余ります。",
        per_group, groups, remainder
    );
}
Playgroundで開く
解答例と解説
fn main() {
    let students = 27;
    let per_group = 4;

    // できあがる班の数をgroupsに束縛せよ
    let groups = students / per_group; 

    // 余る人数をremainderに束縛せよ
    let remainder = students % per_group; 

    println!(
        "{}人ずつの班が{}班でき、{}人余ります。",
        per_group, groups, remainder
    );
}
Playgroundで開く

27 / 4は算数では6.75ですが、整数同士の除算では小数部分が切り捨てられて6になります。四捨五入ではなく切り捨てである点に注意してください。小数の結果が欲しい場合は、あらかじめf64などの浮動小数点型に揃えて計算します(問題06で扱います)。

%は剰余(あまり)を求める演算子です。27 % 4は3になります。「割り切れるかどうか」をn % 2 == 0のように判定する使い方もよく登場します。

06 - 異なる型同士は演算できない

キャスト数値演算に関する問題です。
次のコードは、合計金額を人数で割って1人あたりの金額を求めようとしていますが、コンパイルエラー(E0277)になります。エラーメッセージを読み、totalpeopleの宣言はそのままに、計算している行だけを修正してください。

期待する出力
1人あたり240円です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let total: f64 = 1200.0;
    let people: i32 = 5;
    let per_person = total / people; // エラー: E0277
    println!("1人あたり{}円です。", per_person);
}
Playgroundで開く
解答例と解説
fn main() {
    let total: f64 = 1200.0;
    let people: i32 = 5;
    let per_person = total / people; // エラー: E0277
    let per_person = total / people as f64; // asでf64に揃えてから割る
    println!("1人あたり{}円です。", per_person);
}
Playgroundで開く

エラーメッセージは「cannot divide f64 by i32」、つまり「f64i32で割ることはできない」でした。

多くの言語では、整数と小数を混ぜて計算すると自動的に型が揃えられます。しかしRustは暗黙の型変換を一切行いません。同じ整数同士でもi32i64のようにビット幅が違えばエラーになります。予期しない精度の低下や値の変化を防ぐための設計です。

型を揃えるには値 as 型と書きます。これをキャストと呼びます。ここではpeople as f6455.0として扱い、f64同士の除算にしています。

なおtotal as i32 / peopleと書くと1200 / 5の整数除算になり、今回はたまたま同じ240になりますが、割り切れない場合は切り捨てられてしまいます。金額を小数のまま扱いたいならf64側に揃えるのが適切です。

07 - キャストの挙動

キャストに関する問題です。
次のコードには3つのキャストが登場します。それぞれ何が出力されるかを実行する前に予測し、それからPlaygroundで実行して答え合わせをしてください。

「Playgroundで開く」をクリックして実行してください
fn main() {
    let score = 78.9;
    println!("A: {}", score as i32);

    let big = 300;
    println!("B: {}", big as u8);

    let passed = true;
    println!("C: {}", passed as i32);
}
Playgroundで開く
答えと解説

出力は次のようになります。

A: 78
B: 44
C: 1

1つずつ見ていきましょう。

A: 浮動小数点型から整数型
78.9 as i32は78です。四捨五入ではなく、小数部分を0の方向へ切り捨てます。-78.9 as i32なら-78になります。

B: 大きい整数型から小さい整数型
300 as u8は44です。u8の範囲は0〜255なので300は収まりませんが、キャストはエラーにもパニックにもならず、上位ビットを切り捨てた結果(300 - 256 = 44)が黙って返ります。問題02では範囲外のリテラルがコンパイルエラーになりましたが、asを書くと「承知のうえで変換している」とみなされ、警告なしに値が変わってしまいます。

C: 論理値型から整数型
true as i32は1です(falseなら0)。boolから整数へのキャストは許可されていますが、逆方向の「整数からbool」はできません。

このようにasは失敗を報告してくれない変換です。値が確実に収まると分かっている場合や、切り捨てを意図している場合に限って使うのが安全です。

08 - 論理値型

論理値型に関する問題です。
在庫数から「在庫があるか」と「再入荷が必要か」を判定し、その結果をbool型の変数に束縛して出力してください。

期待する出力
在庫あり: false
再入荷が必要: true
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let stock = 0;

    // stockが0より大きいかどうかの判定結果を、bool型の型注釈付きでin_stockに束縛せよ

    // stockが0と等しいかどうかの判定結果を、bool型の型注釈付きでneeds_restockに束縛せよ

    println!("在庫あり: {}", in_stock);
    println!("再入荷が必要: {}", needs_restock);
}
Playgroundで開く
解答例と解説
fn main() {
    let stock = 0;

    // stockが0より大きいかどうかの判定結果を、bool型の型注釈付きでin_stockに束縛せよ
    let in_stock: bool = stock > 0; 

    // stockが0と等しいかどうかの判定結果を、bool型の型注釈付きでneeds_restockに束縛せよ
    let needs_restock: bool = stock == 0; 

    println!("在庫あり: {}", in_stock);
    println!("再入荷が必要: {}", needs_restock);
}
Playgroundで開く

booltruefalseの2つの値だけを取る型です。>==といった比較演算子はboolの値を生み出すなので、その結果をそのまま変数に束縛できます。

比較演算子は==(等しい)・!=(等しくない)・<<=>>=の6つです。代入の=と等値比較の==は別物なので混同しないよう注意してください。

ここでは学習のために: boolと型注釈を書きましたが、比較の結果がboolであることはコンパイラにも自明なので、実際のコードではlet in_stock = stock > 0;と省略するのが一般的です。

09 - 論理演算子

論理演算子論理値型に関する問題です。
遊園地のアトラクションに乗れるのは、次の条件をすべて満たす場合です。

  • 雨が降っていない
  • 身長が140cm以上
  • 12歳以上、または大人の同伴がある

この条件を1つの式にまとめて、判定結果を出力してください。

期待する出力
乗車できる: true
雨天時に乗車できる: false
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let height = 145; // 身長(cm)
    let age = 10;
    let with_adult = true; // 大人の同伴があるか
    let is_rainy = false;

    // 上の3つの条件をすべて満たすかどうかをcan_rideに束縛せよ

    println!("乗車できる: {}", can_ride);

    // 雨が降ってきた
    let is_rainy = true;

    // 同じ条件で判定し直した結果をcan_ride_in_rainに束縛せよ

    println!("雨天時に乗車できる: {}", can_ride_in_rain);
}
Playgroundで開く
解答例と解説
fn main() {
    let height = 145; // 身長(cm)
    let age = 10;
    let with_adult = true; // 大人の同伴があるか
    let is_rainy = false;

    // 上の3つの条件をすべて満たすかどうかをcan_rideに束縛せよ
    let can_ride = !is_rainy && height >= 140 && (age >= 12 || with_adult); 

    println!("乗車できる: {}", can_ride);

    // 雨が降ってきた
    let is_rainy = true;

    // 同じ条件で判定し直した結果をcan_ride_in_rainに束縛せよ
    let can_ride_in_rain = !is_rainy && height >= 140 && (age >= 12 || with_adult); 

    println!("雨天時に乗車できる: {}", can_ride_in_rain);
}
Playgroundで開く

論理演算子は&&(かつ)・||(または)・!(否定)の3つです。!is_rainyは「雨が降っていない」、つまりis_rainy == falseと同じ意味になります。

括弧の位置が重要です。&&||より優先順位が高いため、age >= 12 || with_adultを括弧で囲まないと「12歳以上」の条件だけが手前の&&に結び付いてしまい、意図と違う判定になります。優先順位を覚えるより、括弧で明示するほうが読み手にも親切です。

1人目の判定は「雨ではない(true)」「145cm ≧ 140cm(true)」「10歳は12歳未満だが同伴あり(true)」がすべて成立してtrueになります。

2つ目のポイントはis_rainyシャドーイングです。let is_rainy = true;で新しいis_rainyを作っても、すでに評価済みのcan_rideの値は変わりません。式は書いた時点で評価され、その結果が変数に束縛されるためです。

10 - 文字型

文字型文字列リテラルに関する問題です。
次のコードは成績評価の1文字を変数に束縛しようとしていますが、コンパイルエラー(E0308)になります。エラーメッセージを読み、grade型注釈は変えずに修正してください。

期待する出力
成績評価はAです。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let grade: char = "A"; // エラー: E0308
    println!("成績評価は{}です。", grade);
}
Playgroundで開く
解答例と解説
fn main() {
    let grade: char = "A"; // エラー: E0308
    let grade: char = 'A'; // シングルクォートで囲む
    println!("成績評価は{}です。", grade);
}
Playgroundで開く

エラーメッセージは「expected char, found &str」、つまり「charが入るはずの変数に文字列が入っている」という型の不一致です。

Rustではクォートの種類で型が変わります。

書き方 意味
'A' char 1文字
"A" &str 文字列(たまたま1文字のもの)

第1章から使ってきた"Hello, world!"のようなダブルクォートの文字列リテラルは、1文字であっても文字列です。1文字を表すcharにはシングルクォートを使います。

charが表すのは「Unicodeスカラー値」1つなので、'あ''🦀'のような文字も1つのcharとして扱えます。逆に、シングルクォートの中に2文字以上を書くとコンパイルエラーになります。

これで第2章は終わりです。基本の型と演算がそろったので、次の章ではこれらを使って処理の流れを分岐・反復させる制御フローに進みます。