📖

「The Rust Programming Language 日本語版」を読んだ備忘録②

2024/05/07に公開

https://zenn.dev/matcha22/articles/63ee2797fe47d6
の続き

6.Enum(列挙型)

取り得る値を列挙することで型の定義が可能
構造体の使い分け
enumの列挙子は、その識別子の元に名前空間分けされていること

簡単なプログラム(数当てゲーム) 一部追加

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

fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Please input your guess.");

        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(_) => {
                // エラー時のコメント追加
                // matchのアームで複数行にわたる場合は{}で囲う
                // このとき、;が必要
                println!("This input is incorrect! Please enter an integer.");
                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;
                }
            }
    }
}

matchを使用したFizzbuzz

fn main() {
    for number in 1..101 {
        match number {
            number if number % 15 ==0 
            
            => println!("Fizzbuzz"),
            number if number % 3 == 0 => println!("Fizz"),
            number if number % 5 == 0 => println!("Buzz"),
            number => println!("{}", number),
        }
    }
}

ここまで書いた後で、色々探してたらとても良い記事を発見した
https://qiita.com/hinastory/items/543ae9749c8bccb9afbc

7.パッケージ、クレート、モジュール

モジュールシステム
└── パッケージ: my_project
├── クレート: my_crate (バイナリ)
│ └── ソースコード: src/main.rs
└── クレート: my_library (ライブラリ)
  └── ソースコード: src/lib.rs

モジュール(mod)

mod front_of_house {
    // pubを付ける
    pub mod hosting {
        // pubを付ける
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    // 絶対パス
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    // 相対パス
    front_of_house::hosting::add_to_waitlist();
}

fn main() {}

上記のコードは、"use"を用いることでより短いコードに書き換えることが出来る

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();

下記のように関数までuseに持ち込むのは慣例的ではないため割けた方が良い
どこでadd_to_waitlistが定義されたのかが不明瞭になるため

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
    add_to_waitlist();
    add_to_waitlist();
}

一方、構造体やenum等をuseで持ち込む場合、フルパスを書くのが慣例的

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
    }

fn main() {}

エイリアス指定

#![allow(unused)]
fn main() {
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
    }
}

glob演算子

#![allow(unused)]
fn main() {
use std::collections::*;
}

8.一般的なコレクション

8.1 ベクタで値のリストを保持する

新しいベクタの生成

fn main() {
    let v: Vec<i32> = Vec::new();
}
fn main() {
    let v = vec![1, 2, 3];
}
fn main() {
    // 空の可変ベクタを生成
    let mut v: Vec<i32> = Vec::new();
}

下記の場合、追加する値から推論可能のため型注釈は不要

fn main() {
    let mut v = Vec::new();
    // ベクタに値を追加
    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
}
fn main() {
    let v = vec![1, 2, 3];
}

ベクタもスコープを抜けると解放される

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    // 下記2行は共に同じ意味
    let does_not_exist = &v[100]; //->パニックを返す
    let does_not_exist = v.get(100); //->Noneを返す
}
fn main() {
    let v = vec![1, 2, 3, 4, 5];
    // 0-indexに注意
    let third: &i32 = &v[2];
    println!("The third element is {}", third);

    match v.get(2) {
        //                      "3つ目の要素は{}です"
        Some(third) => println!("The third element is {}", third),
        //               "3つ目の要素はありません。"
        None => println!("There is no third element."),
    }
}

ベクタの値を順に処理する

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{}", i);
    }
}

Pythonの場合

v = [100, 32, 57]
for i in v:
    print(i)

ベクタの全要素に変更を加わる場合

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
        println!("{}", i);
    }
}

Pythonの場合

v = [100, 32, 57]
for i in v:
    i += 50
    print(i)

Enumを使って、複数の方を保持

fn main() {
    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
}

8.2 文字列

Rustにおいての文字列の定義
一般的にStringと文字列スライスの&strを指す

新規文字列を生成する

#![allow(unused)]
// 未使用の変数や未使用のコードに関するコンパイラの警告を抑制するために使用
fn main() {
let mut s = String::new();
}
#![allow(unused)]
fn main() {
// パターン1:to_stringメソッドを使用して文字列リテラルからStringを生成
let data = "initial contents";
let s = data.to_string();
// パターン2:to_stringメソッドを使用して文字列リテラルから直接Stringを生成
let s = "initial contents".to_string();
// パターン3:String::from関数を使って文字列リテラルからStringを生成
let s = String::from("initial contents");
}

文字列を更新
pushではなく、push_strを使用する必要がある

#![allow(unused)]
fn main() {
let mut s = String::from("foo");
s.push_str("bar");
}

2つの文字列を連結する場合

#![allow(unused)]
fn main() {
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1はムーブされ、もう使用できないことに注意
println!("{}", s3)
}
#![allow(unused)]
fn main() {
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = s1 + "-" + &s2 + "-" + &s3;
}
// pythonに近い文字列の連結方法

Rustで以下の形が一般的

#![allow(unused)]
fn main() {
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3); //format!マクロを使用する
}

8.3 ハッシュマップ

パターン1:空のハッシュマップをnewで作り、要素をinsertで追加する方法

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
}

パターン2:タプルのベクタに対してcollectメソッドを使用する方法

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let teams  = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
// HashMap<_, _>という型注釈が必要
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
}

forループでハッシュマップのキーと値のペアを走査する

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

for (key, value) in &scores {
    println!("{}: {}", key, value);
}
}

Pythonの場合

scores = []
scores.append(("Blue", 10))
scores.append(("Yellow", 50))

for key, value in scores:
    print(key, ":", value)

ハッシュマップを更新する
値を上書きする

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

println!("{:?}", scores);

// output
// {"Blue": 25}
}

キーに値がなかった時のみ値を挿入する

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);

println!("{:?}", scores);

// output
// {"Yellow": 50, "Blue": 10}
}

古い値に基づいて値を更新する


#![allow(unused)]
fn main() {
use std::collections::HashMap;

let text = "hello world wonderful world";

let mut map = HashMap::new();

for word in text.split_whitespace() {
    let count = map.entry(word).or_insert(0);
    *count += 1;
}

println!("{:?}", map);

// output
// {"world": 2, "hello": 1, "wonderful": 1}
}

9.エラー処理

回復可能なエラー->Result<T, E>値

プログラムが回復不能なエラー->実行を中止するpanic!マクロ

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");

    // 失敗理由によって動作を変える
    let f = match f {
        // ファイルが存在し、ファイルを開くことが成功する場合
        Ok(file) => file,
        // ファイルが存在しない場合 かつ ファイル作成は可能の場合
        // パターンマッチングの場合、引数にrefが必要
        Err(ref error) if error.kind() == ErrorKind::NotFound => {
            match File::create("hello.txt") {
                Ok(fc) => fc,

                // ファイルが存在しない場合 かつ ファイル作成も不可能の場合
                Err(e) => {
                    panic!(
                        "Tried to create file but there was a problem: {:?}",
                        e
                    )
                },
            }
        },
        // ファイルは存在するが、開くことに失敗する場合
        Err(error) => {
            panic!(
                "There was a problem opening the file: {:?}",
                error
            )
        },
    };
}

expectを使用してエラー処理を書く場合

use std::fs::File;

fn main() {
    // hello.txtを開くのに失敗しました
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
#![allow(unused)]
fn main() {
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    // 文末の?はmatch文とほぼ同義
    // ?演算子は戻り値にResultを持つ関数でしか使用できない
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
    }
}

第2章の数当てゲームにエラー処理を追加

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

// Guess型の宣言
// 独自の型を作るパターン
pub struct Guess {
    value: u32,
}

// Guess型の実装
// Guess型が1~100の数字であることをここで担保する
impl Guess {
    pub fn new(value: u32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess { value }
    }
// ゲッター
    pub fn value(&self) -> u32 {
        self.value
    }
}

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        // Guess型の新しいインスタンスを作成
        let guess: Guess = match guess.trim().parse() {
            Ok(num) => Guess::new(num),// 変更前:Ok(num) => num, 
            Err(_) => continue,//不正な入力の処理
        };

        println!("You guessed: {}", guess.value());

        // Guess型の値を比較
        match guess.value().cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
//output
// Please input your guess.
//input
// 0
//output
// thread 'main' panicked at 'Guess value must be between 1 and 100, got 0.'

10.ジェネリック型、トレイト、ライフタイム

10.1 ジェネリック型 & 10.2 トレイト

トレイト境界の書き方の一例

見にくい例

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

見やすい例

fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

ジェネリクスを不使用の場合
各型ごとに関数を作成する

fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> char {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest_i32(&number_list);
    println!("The largest number is {}", result);
   assert_eq!(result, 100);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest_char(&char_list);
    println!("The largest char is {}", result);
   assert_eq!(result, 'y');
}

トレイトを使用してジェネリクな型に対して動くように修正した場合
共通の関数を作成する

// Copy:moveするたびに所有権を取るのではなくコピーする(クローンも不要)
// PartialOrd:型に限らず比較することが可能となる
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

10.3 ライフタイム

Rustにおいて、型推論と同様にライフタイムも暗黙的に推論する
ライフタイムの主な目的は、ダングリング参照を回避すること

ジェネリックな型引数、トレイト境界、ライフタイムを全て使用した例

use std::fmt::Display;
// 2つの文字列のうち長い方を返すlongest関数
fn longest_with_an_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display, //トレイト境界の宣言
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest_with_an_announcement(
        string1.as_str(),
        string2,
        "Today is someone's birthday!",
    );
    println!("The longest string is {}", result);
}

Discussion