Progrust Library.

// book chapter

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

第1章 コンソール出力と変数.

この章では、Rustの入り口としてコンソール出力変数を学びます。
println!での出力から始めて、変数の宣言・可変性・定数・シャドーイング、そしてRustの土台となる「文と式」の考え方までを10問で身につけます。

問題の進め方

各問題の冒頭には、その問題に関連する辞書へのリンクを挙げています。問題文ではあえて詳しい説明を省略しているので、まずはリンク先の辞書で必要な知識を確認してから問題に取り組んでください。リンクはマウスを乗せるとその場で解説がプレビュー表示され、クリックすると全文を読めます。

問題のコードブロックに付いている 「Playgroundで開く」ボタンを押すと、Rust Playgroundが新しいタブで開き、コードが貼り付けられた状態になります。そのままブラウザ上で編集・実行できるので、環境構築は不要です。
コードを完成させたら実行し、問題文にある「期待する出力」と一致するか(またはコンパイルが通るか)を確かめてください。答え合わせは各問題の末尾にある「解答例と解説」で行います。

01 - はじめてのRustプログラム

コンソール出力文字列リテラルに関する問題です。
コンソールに「Hello, world!」と出力するコードを記載してください。

期待する出力
Hello, world!
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    // Hello, world! をコンソールに出力せよ

}
Playgroundで開く
解答例と解説
fn main() {
    // Hello, world! をコンソールに出力せよ
    println!("Hello, world!"); 
}
Playgroundで開く

println!は渡した文字列をコンソールに出力し、末尾に改行を加えます。名前の最後に!が付いているのは、関数ではなくマクロだからです。
出力する文字列は"Hello, world!"のようにダブルクォートで囲んで書きます。これを文字列リテラルと呼びます。

02 - 値を埋め込んで出力する

コンソール出力に関する問題です。
println!の文字列中に書いたプレースホルダ{}には、後ろに並べた値が順に埋め込まれます。プレースホルダを2つ使って、値の3360を埋め込んで出力するようにコードを修正してください。

期待する出力
りんごを3個買うと360円です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    // プレースホルダ{}を2つ追加し、3と360を埋め込んで出力せよ
    println!("りんごを個買うと円です。");
}
Playgroundで開く
解答例と解説
fn main() {
    // プレースホルダ{}を2つ追加し、3と360を埋め込んで出力せよ
    println!("りんごを個買うと円です。"); 
    println!("りんごを{}個買うと{}円です。", 3, 360); 
}
Playgroundで開く

{}は書いた順番どおりに、後ろの値と対応します。1つ目の{}3が、2つ目の{}360が入ります。

03 - 変数を宣言する

変数コンソール出力に関する問題です。
適切な変数を作成し、コンソールに「私の名前は次郎です。」を出力するコードを記載してください。

期待する出力
私の名前は次郎です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    // nameという変数名に「次郎」という文字列を束縛した変数を記載せよ

    // 上記の変数を利用して「私の名前は次郎です。」が出力されるように以下コードを修正せよ
    println!("私の名前は太郎です。");
}
Playgroundで開く
解答例と解説
fn main() {
    // nameという変数名に「次郎」という文字列を束縛した変数を記載せよ
    let name = "次郎"; 

    // 上記の変数を利用して「私の名前は次郎です。」が出力されるように以下コードを修正せよ
    println!("私の名前は太郎です。"); 
    println!("私の名前は{}です。", name); 
}
Playgroundで開く

変数はlet 変数名 = 値;で宣言します。
なお、プレースホルダにはprintln!("私の名前は{name}です。");のように変数名を直接書いて埋め込むこともできます。どちらの書き方でも正解です。

04 - 変数はデフォルトで不変

変数に関する問題です。
次のコードは、変数moneyに500を足そうとしていますが、コンパイルエラー(E0384)になります。エラーメッセージを確認し、1行目の変数宣言だけを修正してコンパイルが通るようにしてください。

期待する出力
所持金は1500円です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let money = 1000;
    money = money + 500; // エラー: E0384
    println!("所持金は{}円です。", money);
}
Playgroundで開く
解答例と解説
fn main() {
    let money = 1000; 
    let mut money = 1000; 
    money = money + 500;
    println!("所持金は{}円です。", money);
}
Playgroundで開く

Rustの変数は、宣言しただけでは不変(値を変更できない)です。あとから値を変更したい変数には、宣言時にmutを付けます。
「基本は不変で、変えたいものにだけ印を付ける」というこの設計は、意図しない書き換えによるバグを防ぐRustの特徴のひとつです。

05 - mutで可変にする

変数に関する問題です。
銀行口座の残高を表す変数balanceを作り、入金で値を更新するコードを記載してください。

期待する出力
残高は1000円です。
入金後の残高は1500円です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    // 残高を表す可変の変数balanceを1000で初期化して宣言せよ

    println!("残高は{}円です。", balance);

    // balanceに500を足して更新せよ

    println!("入金後の残高は{}円です。", balance);
}
Playgroundで開く
解答例と解説
fn main() {
    // 残高を表す可変の変数balanceを1000で初期化して宣言せよ
    let mut balance = 1000; 

    println!("残高は{}円です。", balance);

    // balanceに500を足して更新せよ
    balance = balance + 500; 

    println!("入金後の残高は{}円です。", balance);
}
Playgroundで開く

更新はbalance += 500;と書くこともできます(balance = balance + 500;の省略形)。

06 - 定数を宣言する

定数に関する問題です。
次のコードは、チケット価格を定数TICKET_PRICEとして宣言していますが、コンパイルエラーになります。何が足りないのかをエラーメッセージから読み取り、コンパイルが通るように修正してください。
なお、修正の際は「32ビットの符号なし整数型」を表すu32を使ってください(型の詳細は第2章で扱います)。

期待する出力
チケットは1枚1200円です。
3枚では3600円です。
「Playgroundで開く」をクリックして修正・実行してください
const TICKET_PRICE = 1200; // この行でコンパイルエラーになる

fn main() {
    println!("チケットは1枚{}円です。", TICKET_PRICE);
    println!("3枚では{}円です。", TICKET_PRICE * 3);
}
Playgroundで開く
解答例と解説
const TICKET_PRICE = 1200; // この行でコンパイルエラーになる
const TICKET_PRICE: u32 = 1200; 

fn main() {
    println!("チケットは1枚{}円です。", TICKET_PRICE);
    println!("3枚では{}円です。", TICKET_PRICE * 3);
}
Playgroundで開く

エラーメッセージは「missing type for const item」、つまり「型の指定がない」でした。letの変数と違い、定数の宣言はconst 名前: 型 = 値;の形で型注釈が必須です。
名前は慣習としてすべて大文字のSCREAMING_SNAKE_CASEで付けます。また、mutにはできず、関数の外側でも宣言できます。なお、エラーメッセージのhelpは: i32を提案しますが、そちらを指定してもコンパイルは通ります。

07 - シャドーイング

シャドーイングに関する問題です。
同じ名前priceのまま、letでセール価格の変数に作り直すコードを記載してください(mutは使わないこと)。

期待する出力
通常価格は500円です。
セール価格は400円です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let price = 500;
    println!("通常価格は{}円です。", price);

    // シャドーイングで、priceを100円引きの値に作り直せ

    println!("セール価格は{}円です。", price);
}
Playgroundで開く
解答例と解説
fn main() {
    let price = 500;
    println!("通常価格は{}円です。", price);

    // シャドーイングで、priceを100円引きの値に作り直せ
    let price = price - 100; 

    println!("セール価格は{}円です。", price);
}
Playgroundで開く

既存の変数と同じ名前でlet宣言をやり直すことをシャドーイングと呼びます。
再代入ではなく「新しい変数を作って同じ名前で覆い隠す」仕組みなので、mutが付いていない変数にも使えます。

08 - シャドーイングとmutの違い

シャドーイング変数に関する問題です。
次のコードは、文字列のinputを文字数(数値)で上書きしようとしていますが、mutを付けているのにコンパイルエラー(E0308)になります。なぜmutでも再代入できないのかをエラーメッセージから考えてみてください。
そのうえで、mutと再代入をやめて、シャドーイングを使う形に修正してください。

期待する出力
入力は5文字です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let mut input = "12345"; // 文字列
    input = input.len(); // エラー: E0308
    println!("入力は{}文字です。", input);
}
Playgroundで開く
解答例と解説
fn main() {
    let mut input = "12345"; // 文字列
    input = input.len(); // エラー: E0308
    let input = "12345"; // 文字列
    let input = input.len(); // シャドーイングなら型の違う値で作り直せる
    println!("入力は{}文字です。", input);
}
Playgroundで開く

エラーメッセージは「expected &str, found usize」、つまり「文字列が入るはずの変数に数値を入れようとしている」という型の不一致です。mutは「同じ変数の値を書き換える」だけの仕組みなので、型を変えることはできません。一方シャドーイングは「新しい変数を作る」ので、元と違う型にできます。
「文字列で受け取った入力を数値に変換して使う」といった場面で、名前を増やさずに済む定番のテクニックです。

09 - 文と式

に関する問題です。
次のコードは、りんごとみかんの合計金額をtotalに束縛しようとしていますが、このままではtotalに値が入らずコンパイルエラー(E0277)になります。
どのように書けば合計金額がtotalに束縛されるのかを考えて、ブロックの中にコードを追記してください。

期待する出力
合計は250円です。
「Playgroundで開く」をクリックして修正・実行してください
fn main() {
    let total = {
        let apple = 150;
        let orange = 100;
        // 合計金額がtotalに束縛されるように、ここにコードを追記せよ

    };
    println!("合計は{}円です。", total);
}
Playgroundで開く
解答例と解説
fn main() {
    let total = {
        let apple = 150;
        let orange = 100;
        // 合計金額がtotalに束縛されるように、ここにコードを追記せよ
        apple + orange // セミコロンを付けない: この式の値がブロックの値になる
    };
    println!("合計は{}円です。", total);
}
Playgroundで開く

Rustではブロック{}値を生成する式であり、ブロック内の末尾に置いたセミコロンなしの式がブロック全体の値になります。
逆にapple + orangeの後ろにセミコロンを付けると、値を捨てるになってしまい、ブロックの値はユニット型()になります(修正前のエラーE0277は、(){}で出力できないことが原因です)。
「セミコロンなしの式は値を返す」というルールは、この先の関数の戻り値でも重要になります。

10 - 応用: 出力を予測する

第1章の総復習として、定数変数シャドーイングを組み合わせた問題です。
次のコードを実行すると、何が出力されるでしょうか。実行する前に出力を予測し、それからPlaygroundで実行して答え合わせをしてください。

「Playgroundで開く」をクリックして修正・実行してください
const BASE: u32 = 100;

fn main() {
    let price = BASE;
    let mut count = 1;
    count = count + 2;
    let price = price * count;
    {
        let price = price + 50;
        println!("A: {}円", price);
    }
    println!("B: {}円", price);
}
Playgroundで開く
答えと解説

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

A: 350円
B: 300円

コードを上から追ってみましょう。

  1. let price = BASE; — 定数BASEの値100がpriceに束縛されます
  2. countmut付きなので、再代入で1から3に更新されます
  3. let price = price * count; — シャドーイングでpriceは300になります
  4. ブロック内のlet price = price + 50; — さらにシャドーイングされ、このブロックの中だけpriceは350になります(→ A: 350円)
  5. ブロックを抜けると内側のpriceは消え、手前の300のpriceが再び見えるようになります(→ B: 300円)

ブロック内のシャドーイングの効果がブロックの終わりまでで消える点がポイントです。予測が当たっていれば、第1章の内容はばっちりです。