第2回 Rust 勉強会 資料

こんにちは,M2の西です.

乃村研では5月から,Rust勉強会を行っています.
ScrapBox の勉強会概要ページ: https://scrapbox.io/nomlab/Rust%E5%8B%89%E5%BC%B7%E4%BC%9A

今回は,第2回Rust勉強会(2019/5/23 17:00-)の資料を掲載します.

The Rust Programming Language 要約 9-15章

http://doc.rust-jp.rs/book/second-edition/

9章 エラー処理

  • Rustには例外が存在せず,代わりに以下の2種が存在.
    • panic! マクロによる回復不能なエラー
    • Result<T,E> 値による回復可能なエラー

panic! で回復不能なエラー

  • panic! マクロが実行されると,標準ではスタックを巻き戻して終了.
  • Cargo.toml に以下を記述すれば,即座に異常終了する.
[profile]
panic = `abort`
  • panic! マクロの呼び出し例
fn main() {
	panic!("crash and burn"):
}
  • 以下のようなエラーメッセージが出力される
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

バックトレースを使用する

  • 以下のコードはパニックする
fn main() {
	let v = vec![1, 2, 3];
	
	v[99]; //無効な添字
}
  • 実行すると,vec.rspanic! が発生していることが分かる.
$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
     Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
99', /checkout/src/liballoc/vec.rs:1555:10
('main'スレッドは、/checkout/src/liballoc/vec.rs:1555:10の
「境界外番号: 長さは3なのに、添え字は99です」でパニックしました)
note: Run with `RUST_BACKTRACE=1` for a backtrace.
  • 以下のように, RUST_BACKTRACE 環境変数をセットするとバックトレースを得られる.
$ RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /checkout/src/liballoc/vec.rs:1555:10
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
             at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::_print
             at /checkout/src/libstd/sys_common/backtrace.rs:71
   2: std::panicking::default_hook::{{closure}}
             at /checkout/src/libstd/sys_common/backtrace.rs:60
             at /checkout/src/libstd/panicking.rs:381
   3: std::panicking::default_hook
             at /checkout/src/libstd/panicking.rs:397
   4: std::panicking::rust_panic_with_hook
             at /checkout/src/libstd/panicking.rs:611
   5: std::panicking::begin_panic
             at /checkout/src/libstd/panicking.rs:572
   6: std::panicking::begin_panic_fmt
             at /checkout/src/libstd/panicking.rs:522
   7: rust_begin_unwind
             at /checkout/src/libstd/panicking.rs:498
   8: core::panicking::panic_fmt
             at /checkout/src/libcore/panicking.rs:71
   9: core::panicking::panic_bounds_check
             at /checkout/src/libcore/panicking.rs:58
  10: <alloc::vec::Vec<T> as core::ops::index::Index<usize>>::index
             at /checkout/src/liballoc/vec.rs:1555
  11: panic::main
             at src/main.rs:4
  12: __rust_maybe_catch_panic
             at /checkout/src/libpanic_unwind/lib.rs:99
  13: std::rt::lang_start
             at /checkout/src/libstd/panicking.rs:459
             at /checkout/src/libstd/panic.rs:361
             at /checkout/src/libstd/rt.rs:61
  14: main
  15: __libc_start_main
  16: <unknown>

Result で回復可能なエラー

  • Result 型は以下のように定義されている.
enum Result<T, E> {
	Ok(T),
	Err(E),
}
  • TE はジェネリックな型引数(後に10章で述べる)
  • たとえば, File::openResult<T, E> を返す
    • T の型は std::fs::File で, E の型は std::io::Error である.
    • match 式を用いて Result を処理する
use std::fs::File;

fn main() {
	let f = File::open("hello.txt");
	
	let f = match f {
		Ok(file) => file,
		Err(eroor) => {
			panic!("There was a problem opening the file: {:?}", error)
		},
	};
}

色々なエラーにマッチする

  • io::Error には io::ErrorKind が得られる kind メソッドがある
  • io::ErrorKind というenumには,たとえば, ErrorKind::NotFound のようなエラーを表す列挙子がある
use std::fs::File;
use std::io::ErrorKind;
	
fn main() {
	let f = File::open("hello.txt");

	let f = match f {
		Ok(file) => file,
		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
				)
			},
		};
}
  • if error.kind() == ErrorKind::NotFound はマッチガードと呼ばれ,この条件式が偽ならパターンマッチングが継続
  • referror がガード条件式にムーブしないために必要

エラー時にパニックするショートカット: unwrapexpect

  • unwrapResult 値が Ok なら, Ok の中身を返す. Err なら panic! 呼び出し.
use std::fs::File;
 
fn main() {
 let f = File::open("hello.txt").unwrap();
}
  • expectpanic! のエラーメッセージを指定できる.
use std::fs::File;
  
fn main() {
  let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

エラーを委譲する

  • 失敗する可能性のある関数を書く際,関数内でエラー処理する代わりに呼び出し元がどうするかを決められるようにエラーを返せる(エラーの委譲)
  • 以下は, Result<String, io::Error> を返す例
use std::io;
use std::io::Read;
use std::fs::File;
  
fn read_username_from_file() -> Result<String, io::Error> {
  let f = File::open("hello.txt");

	  let mut f = match f {
          Ok(file) => file,
          Err(e) => return Err(e), //オープンに失敗したらErrを早期リターン
	  };

	  let mut s = String::new();

	  match f.read_to_string(&mut s) {
          Ok(_) => Ok(s),
          Err(e) => Err(e),
	  }
  }
  • ? 演算子でエラー委譲を簡単に書ける.以下は,上のコードを ? 演算子を用いて書いたもの.
    • Result 値の直後に ? 演算子を置くと, Ok なら Ok の中身がこの式から返り, Err なら関数全体から Err の中身を返す
    • ? 演算子は Result を返す関数でしか使用できない
use std::io;
use std::io::Read;
use std::fs::File;
  
fn read_username_from_file() -> Result<String, io::Error> {
	  let mut f = File::open("hello.txt")?;
	  let mut s = String::new();
	  f.read_to_string(&mut s)?;
	  Ok(s)
}
  • 連結して短く書ける
use std::io;
use std::io::Read;
use std::fs::File;
  
fn read_username_from_file() -> Result<String, io::Error> {
	  let mut s = String::new();
	  File::open("hello.txt")?.read_to_string(&mut s)?;
	  Ok(s)
}

panic! すべきかどうか

  • Result 値を返しておけば,呼び出し元で panic! することもできる.∴ 基本的に Result を返す方がよい.
  • unwrapexpect は,エラーの処理法を決定する準備ができる前,プロトタイプの段階では便利.
    • エラー処理を厳密にしたいときのマーカーとして残る
  • コンパイラよりもプログラマが情報を持っている場合は, unwrap を呼び出すこともある
    • 以下の場合,プログラマには 127.0.0.1 が正しいIPアドレスであることが分かるため unwrap を使用しても問題ない

      use std::net::IpAddr;
      let home: IpAddr = "127.0.0.1".parse().unwrap;
      

10章 ジェネリック型,トレイト,ライフタイム

ジェネリックなデータ型

関数定義

  • 以下の2つの関数は,引数と戻り値の型のみが異なる
// i32 型のリストから最大値を探す
fn largest_i32(list: &[i32]) -> i32 {
	let mut largest = list[0];
	for &item in list.iter() {
		if item > largest {
			largest = item;
		}
	}
	largest
}

// cahr 型のリストから最大値を探す
fn largest_char(list: &[cahr]) -> char {
	let mut largest = list[0];
	for &item in list.iter() {
		if item > largest {
			largest = item;
		}
	}
	largest
}
  • 上記はまとめて以下のように書ける
    • ジェネリックな関数定義 fn largest<T>(list: &[T]) -> T { では,型引数 T<> 内で宣言
    • <T:PartialOrd + Copy> は後に説明(トレイト境界)

      fn largest<T:PartialOrd + Copy>(list &[T]) -> T {
          let mut largest = list[0];
          for &item in list.iter() {
              if item > largest {
                  largest = item;
              }
          }
          largest
      }
      

構造体定義

  • 以下のように定義できる
    • xy はどちらもジェネリックな型 T なので, xy は同じ型でなければならない
struct Point<T> {
	x: T,
	y: T,
}

fn main() {
	let integer = Point { x: 5, y: 10 }; //ok
	let float = Point { x: 1.0, y: 4.0}; //ok
	let wont_work = Point { x: 5, y: 4.0 }; //error
}
  • xy が異なる型を許容するには以下のようにする
struct Point<T, U> {
	x: T,
	y: U,
}

fn main() {
	let integer = Point { x: 5, y: 10 }; //ok
	let float = Point { x: 1.0, y: 4.0}; //ok
	let integer_and_float = Point { x: 5, y: 4.0 }; //ok
}

enum 定義

  • 列挙子にジェネリックなデータ型を保持する enum を定義できる.
enum Option<T> {
	Some(T),
	None,
}
enum Result<T, E> {
	Ok(T),
	Err(E),
}

メソッド定義

  • ジェネリックな型を使うメソッドを構造体や enum に実装できる
    • Point<T> にメソッドを実装するために, T を使用できるよう impl の直後に宣言しなければならない
struct Point<T> {
	x: T,
	y: T,
}

impl<T> Point<T> {
	fn x(&self) -> &T {
		&self.x
	}
}

fn main() {
	let p = Point { x: 5, y: 10 };
	println!("p.x = {}", p.x()); // p.x = 5
}
  • 構造体定義のジェネリックな型引数は,必ずしもそのメソッドで使用するものと同じにはならない
struct Point<T, U> {
	x: T,
	y: U,
}

impl<T, U> Point<T, U> {
	fn mixup<V, W>(self, other: Point<V,W>) -> Point<T, W> {
		Point {
			x: self.x,
			y: other.y,
		}
	}
}

fn main() {
	let p1 = Point { x: 5, y: 10.4 };
	let p2 = Point { x: "Hello", y: 'c' };
	
	let p3 = p1.mixup(p2);
	
	println!("p3.x = {}, p3.y = {}", p3.x, p3.y); // p3.x = 5, p3.y = c
}

トレイト: 共通の振舞を定義する

  • トレイトは多言語のインタフェースのようなもの
  • 共通の振舞を抽象的に定義できる
  • トレイト境界を使用して,あるジェネリックが特定の振舞のあらゆる型になり得ることを指定できる

トレイトを定義する

  • 例: Summary トレイトの定義
pub trait Summary {
	fn summarize(&self) -> String;
}

トレイトを型に実装する

  • Summary トレイトを NewArticleTweet 型に実装する
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {
	let tweet = Tweet {
		username: String::from("horse_ebooks"),
		content: String::from("of course, as you probably already know, people"),
		reply: false,
		retweet: false,
	};

	println!("1 new tweet: {}", tweet.summarize()); 
	// 1 new tweet: horse_ebooks: of course, as you probably already know, people
}

デフォルト実装

  • 型に実装するときに,メソッドのデフォルト実装を使うか,オーバライドできる
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

impl Summary for NewsArticle {} // デフォルト実装を使う

let article = NewsArticle {
    headline: String::from("Penguins win the Stanley Cup Championship!"),
    location: String::from("Pittsburgh, PA, USA"),
    author: String::from("Iceburgh"),
    content: String::from("The Pittsburgh Penguins once again are the best
    hockey team in the NHL."),
};

println!("New article available! {}", article.summarize());
// New article available! (Read more...)

トレイト境界

  • ジェネリックな型を制限し,型が特定のトレイトや振舞を実装するものに制限できる
    • 以下の例では,ジェネリックな型 T にトレイト境界を使用して itemSummary トレイトを実装する型でなければならないことを指定している
pub fn notify<T: Summary>(item: T) {
	println!("Breaking news! {}", item.summarize());
}
  • + でつないで複数指定もできる
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
  • 上記の記法だと,トレイト境界が多くなると読みづらい.このため, where を用いて以下のように書ける.
fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

トレイト境界を使用して9章の largest 関数を書く

  • > 演算子を使用して型 T の比較を行っているため,標準ライブラリトレイトの PartialOrd が実装されている必要がある
  • i32char のような既知のサイズの型は,スタックに格納できるため, Copy トレイトを実装している.しかし, TCopy トレイトが実装されているとは限らない.Copy トレイトが実装されていないと largest = list[0]; で値のムーブができるエラーとなる
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    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(&number_list);
    println!("The largest number is {}", result); // 100

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

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

トレイト境界を使用してメソッド実装を条件分けする

  • Pair<T> は,常に new 関数を実装.
  • TPartialOrdDisplay が実装されている時のみ cmp_display を実装.
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self {
            x,
            y,
        }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}
  • ブランケット実装:トレイト境界を満たすあらゆる型にトレイトを実装
    • 以下では Display トレイトを実装するあらゆる型に ToString トレイトを実装している
impl<T: Display> ToString for T {
    // --snip--
}

ライフタイムで参照を検証する

  • Rustでは,参照はすべてライフタイムを保持している.ライフタイムとは,その参照が有効になるスコープのこと.
  • 多くの場合,ライフタイムは暗黙的に推論される

ライフタイムでダングリング参照を回避する

  • ライフタイムの主な目的は,ダングリング参照を回避すること
  • ダングリング参照があると,意図しないメモリ参照が発生する可能性あり
  • たとえば,以下はスコープを抜けた参照を使用しようとしている
{
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("r: {}", r);
}
  • 以下のエラーが発生
    • 内側のスコープで rx への参照をセットしようとしている
    • 内側のスコープから抜けた後に, r の値を出力しようとしているが, r が参照している値を使う前にスコープを抜けるためコンパイル不可
error[E0597]: `x` does not live long enough
(エラー: `x`の生存期間が短すぎます)
  --> src/main.rs:7:5
   |
6  |         r = &x;
   |              - borrow occurs here
   |              (借用はここで起きています)
7  |     }
   |     ^ `x` dropped here while still borrowed
   |     (`x`は借用されている間にここでドロップされました)
...
10 | }
   | - borrowed value needs to live until here
   | (借用された値はここまで生きる必要があります)

借用精査機(借用チェッカー)

  • Rustコンパイラには,スコープを比較して全ての借用が有効であるかを決定する借用チェッカーがある
  • 先ほどの例における変数のライフタイムは以下のようになっている
    • r のライフタイムは 'a , x のライフタイムは 'b
    • コンパイラは, 'a'b のライフタイムのメモリを参照していることを確認する.
    • ここで, 'a < 'b なのでコンパイルエラー(参照の対象が参照より短命)
{
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("r: {}", r); //          |
}                         // ---------+
  • コードを修正して以下のようにすると,ダングリング参照がなくなりコンパイルできる
    • データのライフタイムが参照より長いため有効な参照である
{
    let x = 5;            // ----------+-- 'b
                          //           |
    let r = &x;           // --+-- 'a  |
                          //   |       |
    println!("r: {}", r); //   |       |
                          // --+       |
}                         // ----------+

関数のジェネリックなライフタイム

  • 2つの文字列スライスのうち長い方を返す関数
  • longest 関数に引数の所有権を渡したくないため,参照を受け取る
  • しかし,このコードはコンパイルエラー
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
  • エラーメッセージは以下の通り.
    • この関数が返す参照が xy かコンパイラには分からない
    • 借用チェッカーは, xy のライフタイムが戻り値のライフタイムとどう関係しているか分からない
error[E0106]: missing lifetime specifier
(エラー: ライフタイム指定子が不足しています)
 --> src/main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |                                 ^ expected lifetime parameter
  |                                   (ライフタイム引数が予想されます)
  |
  = help: this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from `x` or `y`
  (助言: この関数の戻り値型は借用された値を含んでいますが、
シグニチャは、それが`x`か`y`由来のものなのか宣言していません)
  • 借用チェッカーが解析できるように,参照間の関係を定義するジェネリックなライフタイム引数を追加
    • 引数の全参照と戻り値のライフタイムが同じ 'a であることを表現している
    • ジェネリックなライフタイム 'a は, xy のうち小さい方に等しい具体的なライフタイムになる
    • 返却される参照も, xy のライフタイムと同じだけ有効になる
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
  • 以下は問題ない
fn main() {
    let string1 = String::from("long string is long");

    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("The longest string is {}", result);
    }
}
  • 以下は, string2 がスコープを抜けてから result を使用しているためコンパイルエラー
    • プログラマには, string1 の方が長いことが分かるがコンパイラには分からない
fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

構造体定義のライフタイム注釈

  • 構造体に参照を保持させることもできるが,この場合ライフタイム指定子を付ける必要がある
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.')
        .next()
        .expect("Could not find a '.'");  // '.'が見つかりませんでした
    let i = ImportantExcerpt { part: first_sentence };
}

ライフタイム省略

  • 全参照にはライフタイムがあり,参照を使用する関数や構造体にはライフタイム引数を指定する必要がある
  • 借用チェッカーがライフタイムを推論できる場合は省略可
  • ライフタイム省略規則:以下の規則により推論できるなら省略可能
    • 参照である各引数は,独自のライフタイム引数を得る(引数の数だけライフタイム引数を付ける)

    fn foo<'a>(x: &'a i32) , fn bar<'a, 'b>(x: &'a i32, y: 'b i32)

    • 1つだけ入力ライフタイム引数があるなら,そのライフタイムが全ての出力ライフタイム引数に代入される

    fn foo<'a>(x: &'a i32) -> &'a i32

    • 複数の入力ライフタイム引数があるが,メソッドの場合 self のライフタイムが全出力のライフタイム引数に代入される

静的ライフタイム

  • 'static は特殊なライフタイムでプログラム全体の期間を示す.
  • たとえば,文字列リテラルはすべて 'static ライフタイムである
// 静的ライフタイムを持ってるよ
let s: &'static str = "I have a static lifetime.";

13章 関数型言語の機能: イテレータとクロージャ

クロージャ: 環境をキャプチャできる匿名関数

  • Rustのクロージャは変数に保存したり,引数として他の関数に渡すことができる匿名関数

    クロージャで動作の抽象化を行う

  • 以下の関数と同じ動作をするクロージャ
use std::thread;
use std::time::Duration;

fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    intensity
}
use std::thread;
use std::time::Duration;

let expensive_closure = |num| {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};
expensive_closure(5);
  • クロージャでは,引数や戻り値の型を省略可
  • 以下のように明示することもできる
use std::thread;
use std::time::Duration;

let expensive_closure = |num: u32| -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};
  • 以下のように,1回目に String を引数にして呼びだした後, u32 を引数に2回目の呼び出しを行うとエラーになる
    • 1回目の呼び出し時点で,コンパイラが引数と戻り値の型を String と推論するため
let example_closure = |x| x;

let s = example_closure(String::from("hello"));
let n = example_closure(5); //error
error[E0308]: mismatched types
 --> src/main.rs
  |
  | let n = example_closure(5);
  |                         ^ expected struct `std::string::String`, found
  integral variable
  |
  = note: expected type `std::string::String`
             found type `{integer}`

ジェネリック引数と Fn トレイトを使用してクロージャを保存する

  • Fn トレイトは標準ライブラリで用意されており,クロージャはどれかを実装している
  • 以下は引数と戻り値の型が u32 のクロージャを保存する構造体で,トレイト境界に Fn(u32) -> u32 を指定している
struct Cacher<T>
    where T: Fn(u32) -> u32
{
    calculation: T,
    value: Option<u32>,
}

クロージャで環境をキャプチャする

  • 環境をキャプチャし,自分が定義されたスコープの変数にアクセスできる
  • 以下は, equal_to_x 変数に保持されたクロージャを囲む環境から x を使用する例
fn main() {
    let x = 4;

    let equal_to_x = |z| z == x;

    let y = 4;

    assert!(equal_to_x(y));
}
  • クロージャは3つの方法で環境から値をキャプチャできる
    • FnOnce : スコープからキャプチャした変数の所有権を奪い自信にムーブする
    • FnMut : 可変で値を借用する
    • Fn : 不変で値を借用する
  • クロージャを生成するとき,環境を使用する方法に基づいてコンパイラがどのトレイトを使用するか推論
    • 上記の例では, x を読む必要しかないため Fn トレイト
  • move を使用してクロージャに所有権を奪うことを強制することもできる
fn main() {
    let x = vec![1, 2, 3];

	// Vec<i32> は Copy トレイトを実装しないためムーブがおきる
    let equal_to_x = move |z| z == x;

    // ここでは、xを使用できません: {:?}
    println!("can't use x here: {:?}", x);

    let y = vec![1, 2, 3];

    assert!(equal_to_x(y));
}

一連の要素をイテレータで処理する

  • イテレータを使うことで一連の要素に順番に何らかの作業を行うことができる
  • v1 のイテレータを生成して,forループ内で順番に処理
let v1 = vec![1, 2, 3];

let v1_iter = v1.iter();

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

Iterator トレイトと next メソッド

  • イテレータは Iterator トレイトを実装し, next メソッドが実装されている
  • next メソッドは, Some に包まれたイテレータの1要素を返し,終わったら None を返す
  • next は,呼び出すとイテレータを消費するため,ミュータブルである必要がある
#[test]
fn iterator_demonstration() {
    let v1 = vec![1, 2, 3];

    let mut v1_iter = v1.iter();

    assert_eq!(v1_iter.next(), Some(&1));
    assert_eq!(v1_iter.next(), Some(&2));
    assert_eq!(v1_iter.next(), Some(&3));
    assert_eq!(v1_iter.next(), None);
}

イテレータを消費するメソッド

  • たとえば, sum は内部で next を使っているためイテレータを消費する
  • つまり, sum 呼出し後は v1_iter を使用できない
#[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);
}

他のイテレータを生成するメソッド

  • map は各要素に対して呼び出すクロージャを取り,新しいイテレータを生成する
let v1: Vec<i32> = vec![1, 2, 3];

let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); // collect でイテレータを消費してベクタに集結

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

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

  • イテレータの Item 関連型を u32 に設定することで u32 の値を返す
  • next メソッドを実装
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 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);
}
  • Iterator トレイトを実装したので, mapzip も使えるようになる
    • これらが next で実現されているため
#[test]
fn using_other_iterator_trait_methods() {
    let sum: u32 = Counter::new().zip(Counter::new().skip(1))
                                 .map(|(a, b)| a * b)
                                 .filter(|x| x % 3 == 0)
                                 .sum();
    assert_eq!(18, sum);
}

15章 スマートポインタ

  • 参照 & はRustで最もよく使うポインタ
  • スマートポインタは,ポインタのように振る舞うだけでなく追加のメタデータと能力を持つ
    • 所有者をカウントするポインタなど
  • StringVec<T> もスマートポインタ
    • String は,キャパシティなどのメタデータを持っており,データが有効なUTF-8であることを保証する能力がある

ヒープに値を保存する Box<T>

  • スタックではなく,ヒープにデータを格納できる.スタックには,ヒープデータへのポインタが残る.
  • 以下のような場面で有用
    • コンパイル時にサイズを知ることができない型があり,正確なサイズを要求する文脈で使用したいとき
    • 所有権を転送したいが,その時にデータがコピーされない(ムーブする)ことを保証したいとき
    • 値を所有する必要があり,特定の型ではなく特定のトレイトを実装する型であることのみ気にかけているとき
  • 以下は Box<T> を使ってヒープに i32 の値を格納している(普通はやらないが…)
fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}
  • ボックスで再帰的な型を可能にできる
    • 以下の例では, List 値を格納するのに必要な領域が計算できない
enum List {
    Cons(i32, List),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1, Cons(2, Cons(3, Nil)));
}
error[E0072]: recursive type `List` has infinite size
(エラー: 再帰的な型`List`は無限のサイズです)
 --> src/main.rs:1:1
  |
1 | enum List {
  | ^^^^^^^^^ recursive type has infinite size
2 |     Cons(i32, List),
  |               ----- recursive without indirection
  |
  = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to
  make `List` representable
  (助言: 間接参照(例: `Box`、`Rc`、あるいは`&`)をどこかに挿入して、`List`を表現可能にしてください)
  • 以下のようにするとコンパイルできる
    • Box<T> はポインタなのでサイズは既知.∴ サイズの計算が可能
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1,
        Box::new(Cons(2,
            Box::new(Cons(3,
                Box::new(Nil))))));
}

複数の所有権を可能にする参照カウント型の Rc<T>

  • グラフデータ構造では複数の辺が同じノードを指す可能性があり,1つの値が複数の所有者を持つ
  • Rc<T> 型は参照の数を追跡し,カウントが0になるとドロップする
  • データはヒープに保存される
  • 以下の図のような所有権を共有するリストを作ることを考える

!!!図15-3を入れる!!!

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

use List::{Cons, Nil};

fn main() {
    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
    let b = Cons(3, Box::new(a)); // aのムーブ
    let c = Cons(4, Box::new(a)); // ムーブされているため使用不可
}
  • 以下のように, Box<T> の代わりに Rc<T> を使用するとコンパイルできる
    • Rc::clone を呼ぶたびに参照カウントが増える
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

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

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); // count = 1
    let b = Cons(3, Rc::clone(&a)); // count = 2
    let c = Cons(4, Rc::clone(&a)); // count = 3
}

RefCell<T> と内部可変性パターン

  • 内部可変性は,あるデータへの不変参照があるときでもデータを可変化できるRustのデザインパターン
  • 借用規則
    • いかなる時も(以下の両方ではなく、)1つの可変参照かいくつもの不変参照のどちらかが可能になる
    • 参照は常に有効でなければならない。
  • 借用規則をコンパイル時ではなく,実行時に精査する.つまり,プログラマが保証する必要がある.
  • 借用規則を侵害した場合, panic!

不変値への可変借用

  • 以下はコンパイルエラー
    • RefCell<T> を用いると可変化できる
fn main() {
    let x = 5;
    let y = &mut x; // error: 不変値の可変借用はできない
}
  • borrow_mut で可変参照を作成できる
use std::cell::RefCell;

let x = RefCell::new(5);
*x.borrow_mut() += 10;
println!("{:?}", x); // RefCell { value: 15 }


Comment

No comment