LoginSignup
1
1

The Rust Programming Languageを読み進める(1〜2章)

Last updated at Posted at 2024-04-30

はじめに

Rustのコードを、既存コードの横並びで書くことが多い。きちんとドキュメントを読んだことがないので、The Rust Programming Languageを頭から読み進めてみる。読みながら要点をまとめていった記事です。

1章:事始め

Hello world

fn main() {
    println!("Hello, world!");
}
  • Rustのインデントスタイルは4スペース
  • println!は関数呼び出しではなく、マクロ呼び出し
    • let v = vec![1, 2, 3]; は、マクロ無しで書くとv = Vec::new(); v.push(1); v.push(2); ...と長くなる
  • RustはAOT (Ahead-Of-Time) コンパイル言語のため、実行可能ファイルを配布可能
    • AOTコンパイル:実行前に機械語へ変換。実行時は速いが、ソースコードの状態で配布不可
    • JIT (Just in Time) コンパイル:実行時に機械語へ変換。コンパイル時に時間が掛かり最適化もされないが、ソースコードの状態で配布可能

Cargo

[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"

[dependencies]
  • Cargoとは、ビルドシステム&パッケージマネージャ
    • 依存ライブラリのダウンロードや、付随するビルドを実施
    • 設定ファイルは、TOML (Tom's Obvious Minimal Language) 形式
      • 設定ファイルのフォーマットの一種で、名の通りMinimal化されている
      • [package]:パッケージ情報の定義セクション
      • [dependencies]:依存するパッケージの定義セクション
  • Cargo.lockはビルド時に生成され、ビルド時の全ライブラリのバージョンが記録
  • 主要コマンド
    • cargo new:プロジェクトを作成
    • cargo build:プロジェクトをビルド
    • cargo run:プロジェクトのビルドと実行を1ステップで実施
    • cargo check:バイナリ生成せずプロジェクトをビルド。エラー有無確認などに使う

2章:数当てゲームのプログラミング

数を入力するだけの処理

use std::io;

fn main() {
    println!("Guess the number!"); 
    println!("Please input your guess."); 
    let mut guess = String::new();
    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");
    println!("You guessed: {}", guess); 
}
$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 6.44s
     Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6

以下、各行の処理に注目する。

use std::io;
  • Rustには多様なライブラリが存在するが、useを取り込むことで取り込める
  • ただし量が多くなることもあるため、Preludeという仕組みも提供している
    • Rustのライブラリをリストとして内包し、使いたいトレイトにアクセスしやすくなる
    • 例:use std::io::prelude::*;
    let mut guess = String::new();
  • let:変数定義(変数の束縛)
  • mut:Rustの変数がデフォでimmutable(不変)のため、mutable(可変)として定義
  • :::関連関数。Stringに関連付けられた関数newを呼び出し、戻り値をguessに束縛
    io::stdin()
        .read_line(&mut guess)
  • io::stdin():標準入力のためのインスタンスを返却
  • read_line(&mut guess):ユーザからの入力を得て、内容をguessに格納
    • &は引数が参照であることを表し、参照自体が可変。メモリに複数書き込みをせずとも、他のところから同じデータにアクセス可能
    • Rustは変数だけではなく、参照もデフォルトで不変
  • read_line:Result型(列挙型)のOkまたはErrを返す
        .expect("Failed to read line");
  • expect:ReturnされたResult型の結果がOkの場合に値を返し、Errの場合はプログラムが終了
  • expect無しでは、返ってきたResultが使われていないことが警告される

乱数を生成・比較するロジックを加える

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");
    
    let secret_number = rand::thread_rng().gen_range(1..101);
    
    loop {
        println!("Please input your guess.");
        println!("The secret number is: {}", secret_number);
        
        let mut guess = String::new();
        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");
    
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };
        println!("You guessed: {}", guess);
        
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
     Running `target/debug/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
  76
You guessed: 76
Too big!

以下、各行の処理に注目する。

use rand::Rng;
  • randクレートを使うために、tomlファイルにrand = "0.8.3"を追加
    • ^0.8.3の省略記法であり、0.8.3以上0.9.0未満のバージョンを表す
  • Cargoはセマンティックバージョニングに基づいて取得するパッケージを決定 (X.Y.Z)
    • X:後方互換のないメジャーバージョン
    • Y:後方互換のあるマイナーバージョン
    • Z:後方互換のあるパッチパージョン
  • 常に決まったクレートを使いたければ、Cargo.lockをGit管理にする
  • Cargoはリストアップされているクレートをチェックし、未取得対象があればダウンロード。取得済のクレートは、再度ダウンロードすることがない
    • 参照先はcretes.io (https://crates.io/)
    • cargo updateでバージョンアップデートし、lockファイルに記録
    let secret_number = rand::thread_rng().gen_range(1..101);
  • rand::thread_rng:乱数生成器の初期化
  • gen_range(low, high):lowからhigh-1の範囲内で乱数を生成
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };
  • 入力した値のguessはString型、一方でsecret_numberは数値型であるため型不一致。そこで、Stringが用意するparseで数値型に変換
  • parseResultを返すため、matchErr時(文字列の入力)に再入力させている
    • _はすべての値にマッチ
  • trim()はEnter入力時に改行されるため、\nを消すために使用
  • guessの再代入部分はシャドーイングと呼び、同じスコープ内の変数が再宣言可能
    • let mutを宣言して値を変えるのとは性質が異なり、使用する型自体を変更可能。よって、値は同じだけれど、異なる方として定義したい場合に使用
  • どの型としてシャドーイングしたいかを宣言するため、u32は明示的に必要
    • secret_numberu32であるべきと推論できる
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
  • 入力値guessと、乱数で生成したsecret_numberを比較し、Orderingの列挙子を返却
  • matchは式がパターンと一致するか判定
    • パターン => 式をアームと呼ぶ
    • 取り得る値は全て網羅する必要があり、必要に応じて_を使う
  • OrderingLess, Greater, Equalを持つ列挙型
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1