3567
nishi
2019/05/23
0
こんにちは,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.rs
でpanic!
が発生していることが分かる.
$ 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),
}
T
とE
はジェネリックな型引数(後に10章で述べる)- たとえば,
File::open
はResult<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
はマッチガードと呼ばれ,この条件式が偽ならパターンマッチングが継続ref
はerror
がガード条件式にムーブしないために必要
エラー時にパニックするショートカット: unwrap
と expect
unwrap
はResult
値がOk
なら,Ok
の中身を返す.Err
ならpanic!
呼び出し.
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
expect
はpanic!
のエラーメッセージを指定できる.
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
を返す方がよい.unwrap
やexpect
は,エラーの処理法を決定する準備ができる前,プロトタイプの段階では便利.- エラー処理を厳密にしたいときのマーカーとして残る
- コンパイラよりもプログラマが情報を持っている場合は,
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 }
- ジェネリックな関数定義
構造体定義
- 以下のように定義できる
x
とy
はどちらもジェネリックな型T
なので,x
とy
は同じ型でなければならない
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
}
x
とy
が異なる型を許容するには以下のようにする
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
トレイトをNewArticle
とTweet
型に実装する
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
にトレイト境界を使用してitem
がSummary
トレイトを実装する型でなければならないことを指定している
- 以下の例では,ジェネリックな型
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
が実装されている必要があるi32
やchar
のような既知のサイズの型は,スタックに格納できるため,Copy
トレイトを実装している.しかし,T
はCopy
トレイトが実装されているとは限らない.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
関数を実装.T
にPartialOrd
とDisplay
が実装されている時のみ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);
}
- 以下のエラーが発生
- 内側のスコープで
r
にx
への参照をセットしようとしている - 内側のスコープから抜けた後に,
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
}
}
- エラーメッセージは以下の通り.
- この関数が返す参照が
x
かy
かコンパイラには分からない - 借用チェッカーは,
x
とy
のライフタイムが戻り値のライフタイムとどう関係しているか分からない
- この関数が返す参照が
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
は,x
とy
のうち小さい方に等しい具体的なライフタイムになる - 返却される参照も,
x
かy
のライフタイムと同じだけ有効になる
- 引数の全参照と戻り値のライフタイムが同じ
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
と推論するため
- 1回目の呼び出し時点で,コンパイラが引数と戻り値の型を
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
トレイトを実装したので,map
やzip
も使えるようになる- これらが
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で最もよく使うポインタ - スマートポインタは,ポインタのように振る舞うだけでなく追加のメタデータと能力を持つ
- 所有者をカウントするポインタなど
String
やVec<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 }
No comment