📖

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

2024/05/13に公開

https://zenn.dev/matcha22/articles/1f5bdb36237150
の続き

13.イテレータとクロージャ

クロージャ:変数に保存できる関数に似た文法要素

「The Rust Programming Language」の補助資料として、下記の動画を参考にした
参考:https://www.youtube.com/watch?v=tw2WCjBTgRM&t=11265s
難しい部分を嚙み砕いて説明してくれているのでとてもありがたい動画

fn main(){

fn add_one_v1 (x:u32) -> u32{
    x+1
}

let add_one_v2 = |x: u32| -> u32 { x + 1 };
// 型推論出来るため、型は省略可
let add_one_v3 = |x| x + 1;

println!("add_one_v1: {}", add_one_v1(1));
println!("add_one_v2: {}", add_one_v2(1));
println!("add_one_v3: {}", add_one_v3(1));
// 一度推論すると型は確定するため、整数型のあと、浮動小数点型には変更できない
println!("add_one_v3: {}", add_one_v3(1.0)); //error
}

イテレータ:一連の要素を処理する方法

let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();

for val in v1_iter {
    println!("Got: {}", val);
}
// output
// Got: 1
// Got: 2
// Got: 3

イテレータを消費するメソッド
例えば、sumは呼び出し対象のイテレータの所有権を奪うので、sum呼び出し後にv1_iterを使用することはできない


#![allow(unused)]
fn main() {
#[test]
fn iterator_sum() {
    let v1 = vec![1, 2, 3];
    let v1_iter = v1.iter();
    let total: i32 = v1_iter.sum();

    assert_eq!(total, 6);
}
}

イテレータを別の種類のイテレータに変えるメソッド

#![allow(unused)]
fn main() {
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

assert_eq!(v2, vec![2, 3, 4]);
}

クロージャとイテレータを使用した例

#![allow(unused)]
fn main() {
#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter()
        .filter(|s| s.size == shoe_size) //s.size == shoe_sizeのときのみベクタに戻す関数
        .collect()
}

#[test]
fn filters_by_size() {
    let shoes = vec![
        Shoe { size: 10, style: String::from("sneaker") },
        Shoe { size: 13, style: String::from("sandal") },
        Shoe { size: 10, style: String::from("boot") },
    ];

    let in_my_size = shoes_in_my_size(shoes, 10);

    assert_eq!(
        in_my_size,
        vec![
            Shoe { size: 10, style: String::from("sneaker") },
            Shoe { size: 10, style: String::from("boot") },
        ]
    );
}
}

参考サイト:https://doc.rust-lang.org/std/iter/trait.Iterator.html

独自のイテレータを作成する

#![allow(unused)]
fn main() {
struct Counter {
    count: u32,
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;

        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

#[test]
fn calling_next_directly() {
    let mut counter = Counter::new();

    assert_eq!(counter.next(), Some(1));
    assert_eq!(counter.next(), Some(2));
    assert_eq!(counter.next(), Some(3));
    assert_eq!(counter.next(), Some(4));
    assert_eq!(counter.next(), Some(5));
    assert_eq!(counter.next(), None);
}
}
#![allow(unused)]
fn main() {
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;

        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

#[test]
fn using_other_iterator_trait_methods() {
    let sum: u32 = Counter::new().zip(Counter::new().skip(1))
                                 .map(|(a, b)| a * b) //(1*2, 2*3, 3*4, 4*5の組を作る)
                                 .filter(|x| x % 3 == 0) //条件を満たす2*3, 3*4の組のみを加算
                                 .sum(); //2*3 + 3*4 = 18
    assert_eq!(18, sum);
}
}

section12のプログラムをイテレータを用いたものに修正する
src/main.rs

extern crate minigrep;

use std::env;
use std::process;

use minigrep::Config;

fn main() {
    let config = Config::new(env::args()).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {}", err);
        process::exit(1);
    });


    if let Err(e) = minigrep::run(config) {
        eprintln!("Application error: {}", e);
        process::exit(1);
    }
}

src/lib.rs

use std::fs::File;
use std::io::prelude::*;
use std::error::Error;
use std::env;

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool,
}

// 修正前
// impl Config {
//     pub fn new(args: &[String]) -> Result<Config, &'static str> {
//         if args.len() < 3 {
//             return Err("not enough arguments");
//         }
        // clone()を2回使用しているが、極力避けたい
//         let query = args[1].clone();
//         let filename = args[2].clone();
//         let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); //追加

//         Ok(Config { query, filename, case_sensitive})
//     }
// }

// 修正後(clone()を使用せずに同様のコードを書く)
impl Config {
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a file name"),
        };

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config { query, filename, case_sensitive })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let mut f = File::open(config.filename)?;

    let mut contents = String::new();
    f.read_to_string(&mut contents)?;

    let results = if config.case_sensitive {
        search(&config.query, &contents)
    } else {
        search_case_insensitive(&config.query, &contents)
    };

    for line in results {
        println!("{}", line);
    }

    Ok(())
}

// 修正前
// pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
//     let mut results = Vec::new();

//     for line in contents.lines() {
//         if line.contains(query) {
//             results.push(line);
//         }
//     }

//     results
// }

// 修正後
// イテレータアダプタのメソッドを使用して簡潔に書く
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents.lines()
        .filter(|line| line.contains(query))
        .collect()
}

// テストの追加
#[warn(unused_imports)]
#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn one_result() {
        let query = "saf"; //検索したい単語
        let contents = "\
Rust:
safe, fast, productive.
Pick three.";

        assert_eq!(
            vec!["safe, fast, productive."], //検索単語が含まれる一文
            search(query, contents)
        );
    }
}

// PowerShellを使用している場合は、以下の2コマンドで実行する必要がある
// $env:CASE_INSENSITIVE=1
// cargo run to poem.txt

14.cargo

cargo build //通常のビルド
cargo build --release //リリース用の設定でビルド
// opt-levelはコンパイル時間は短いが、生成したコードの動作が遅くなるため、
// 開発時は0、リリース時は3を選択するのが一般的
[profile.dev]
opt-level = 0 //コンパイル時間は短いが、生成したコードの動作が遅い

[profile.release]
opt-level = 3 //コンパイル時間は長いが、生成したコード動作が速い

Crates.ioにクレートを一般公開する

/// Adds one to the number given.
/// 与えられた数値に1を足す。
///
/// # Examples
///
/// ```
/// let five = 5;
///
/// assert_eq!(6, my_crate::add_one(5));
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

ファイル名: Cargo.toml

[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]

上記のように必要な情報をCargo.tomlに書き込み、下記コマンドでクレートを公開する

cargo publish

15.スマートポインタ

1.BOX

スマートポインタの一つに"BOX<T>"がある。
ボックスを使うことで、スタックではなく、ヒープにデータを格納することが出来る。
その際、スタックに残るのはヒープデータへのポインタである。

スタックの場合と同じようにBox<T>を使ってヒープにデータを格納する例

fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}

Box<T>を参照のように使用する

fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

// BOXを使用しない場合エラーが生じる
// 異なる型だから数値と数値への参照の比較は許されていない
fn main() {
    let x = 5;
    let y = &x;

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

2.Deref

Derefトレイト(標準ライブラリ)を実装して型を参照のように扱う
deref:selfを借用し、 内部のデータへの参照を返すメソッド

#![allow(unused)]
fn main() {
use std::ops::Deref;

struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
    type Target = T; //関連型を定義

    fn deref(&self) -> &T {
        &self.0
    }
}
}

参照外し型強制が可変性と相互作用する方法
Derefトレイトを使用して不変参照に対してをオーバーライドするように、 DerefMutトレイトを使用して可変参照の演算子をオーバーライドすることは可能である。

具体的な例を以下に3つ挙げる
T: Deref<Target=U>の時、&Tから&U = 不変参照 -> 不変参照
T: DerefMut<Target=U>の時、&mut Tから&mut U = 可変参照 -> 可変参照
T: Deref<Target=U>の時、&mut Tから&U = 可変参照 -> 不変参照
一方で、不変参照 -> 可変参照は不可である。

3.Drop

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        // CustomSmartPointerをデータ`{}`とともにドロップするよ
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer { data: String::from("my stuff") };      // 俺のもの
    let d = CustomSmartPointer { data: String::from("other stuff") };   // 別のもの
    println!("CustomSmartPointers created.");                           // CustomSmartPointerが生成された
}

4.Rc<T>

Rc形:reference counting(参照カウント)の省略形

enum List {
    // Cons(i32, Box<List>),
    Cons(i32, Rc<List>),
    Nil,
}

use List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    // let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); //BOXではなくRc型を使用する
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}

5.RefCell<T>

RefCell<T>型は、Rc<T>と異なり保持するデータに対して単独の所有権を表す
RefCell<T>を抱えるRc<T>があれば、 複数の所有者を持ち可変化できる値を得ることができる

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

// output
// a after = Cons(RefCell { value: 15 }, Nil)
// b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
// c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
GitHubで編集を提案

Discussion