Share
Embed
Transcript
58 slides
-
プロジェクトの作成 新しくプロジェクトを作りましょう。 「Hello, World」できることを確認します。 $ cargo new grep-rs $ cd
grep-rs $ cargo run 6 -
指定したパスのファイルを読み込む std::fs::read_to_string という関数を使うと、引数で指定したパスから⽂字列とし て内容を読み込みます。 Ok や Err という⾒慣れない⽂字が出てきましたね。 fn main()
{ let path = "./src/main.rs"; match std::fs::read_to_string(path) { Ok(content) => print!("{}", content), Err(why) => println!("{}", why), } } 8 -
Result : エラーハンドリングを⾏う Rust では Result 型を⽤いてエラーハンドリングをします。 正常系だった場合は Ok で囲んで返します。
エラーが送出したい場所に対して、 Err で囲んで返します。 fn division(dividened: i32, divisor: i32) -> Result<i32, CalcError> { if divisor == 0 { return Err(CalcError::DividedByZero); } if dividened < 0 { return Err(CalcError::DetectedNegative(dividened)); } if divisor < 0 { return Err(CalcError::DetectedNegative(divisor)); } Ok(dividened / divisor) } 9 -
Result : エラーハンドリングを⾏う エラー型⾃体も enum で記述することが多いです。 実運⽤では anyhow と thiserror
というクレートを組み合わせて作ります。 enum CalcError { // ゼロ除算を⾏った場合に返すエラー DividedByZero, // 負の数が⼊っていた場合に返すエラー。中にその数値を⼊れる。 DetectedNegative(i32), } 10 -
Result : エラーハンドリングを⾏う Result は enum なので、match 式で分岐を記述できます。 fn main()
{ let answer = division(4, 2); match answer { Ok(value) => println!("answer is {}", value), Err(err) => match err { // eprintln! マクロはエラー出⼒をできる。 CalcError::DividedByZero => eprintln!(" ゼロ除算です"), CalcError::DetectedNegative(num) => { eprintln!("{} は負の数です。負の数は⼊れられません。", num) } }, } } 11 -
ファイルを読み込んだ際のエラーハンドリング というわけで、 Ok や Err でエラーハンドリングをしていることがわかります。 たとえばファイルが⾒つからなかった場合には、 Err 側に⼊ってきます。 fn
main() { let path = "./src/main.rs"; match std::fs::read_to_string(path) { Ok(content) => print!("{}", content), Err(why) => println!("{}", why), } } 12 -
急いでいるときに使える unwrap コードを書いていると、急いでいる場⾯があると思います。 プロダクションではできるかぎりエラーハンドリングをするべきですが、急ぎのときは unwrap という関数を使⽤できます。 あるいは、絶対にエラーにならないはずの場所であえて unwrap を使っておくこともあ ります。
エラーだった場合は、その時点でパニック(プログラムが強制的に異常終了)します。 fn main() { let path = "./src/main.rs"; let content = std::fs::read_to_string(path).unwrap(); print!("{}", content); } 13 -
ファイルパスは実⾏時の引数から渡せるようにする さきほどの例では、ファイルパスはハードコーディングでした。実⾏時に引数で渡せる ようにすると、柔軟になるでしょう。 std::env::args という関数を使うと、実⾏時引数を取得できます。 nth という関数を実⾏すると、何番⽬の引数を取るかを取得できます。 fn main() {
let mut args = std::env::args(); match args.nth(1) { Some(path) => match std::fs::read_to_string(path) { Ok(content) => print!("{}", content), Err(why) => println!("{}", why), }, None => println!("1 つ⽬の実⾏時引数にファイルパスを⼊れる必要があります。") } } 14 -
⼊れ⼦はちょっと読みにくいので、関数を出す ネストが発⽣しました。好みの問題ではありますが、ネストは⼀般に読みにくさを増し ます。 ファイルの読み込み処理を関数に切り出しましょう。 ところで、 Some や None という⾒慣れない⽂字が出てきています。 fn
run(path: String) { match std::fs::read_to_string(path) { Ok(content) => print!("{}", content), Err(why) => println!("{}", why), } } fn main() { let mut args = std::env::args(); match args.nth(1) { Some(path) => run(path), None => println!("1 つ⽬の実⾏時引数にファイルパスを⼊れる必要があります。") } } 15 -
「ない」を⽰す Option null あるいは「ないこと」を⽰すには Option 型を使います。 Option も enum なので、match
式で分岐を記述できます。 fn find(source: Vec<i32>, target: i32) -> Option<i32> { for s in source.into_iter() { if s == target { return Some(s) } } None } fn main() { let vec = vec![1, 2, 3, 4]; match find(vec, 3) { Some(value) => println!("value: {}", value), None => println!("not found!") } } 16 -
急いでいるときに使える unwrap Result と同様に unwrap 関数が⽤意されています。 None に対して unwrap が⾏われるとパニックします。
fn find(source: Vec<i32>, target: i32) -> Option<i32> { for s in source.into_iter() { if s == target { return Some(s) } } None } fn main() { let vec = vec![1, 2, 3, 4]; let value = find(vec, 3).unwrap(); println!("value: {}", value); } 17 -
実⾏してみよう cargo run [ 読み込んでみたいパス] で実⾏できます。 fn run(path: String) {
match std::fs::read_to_string(path) { Ok(content) => print!("{}", content), Err(why) => println!("{}", why), } } fn main() { let mut args = std::env::args(); match args.nth(1) { Some(path) => run(path), None => println!("1 つ⽬の実⾏時引数にファイルパスを⼊れる必要があります。") } } 18 -
なぜ Result ? Either のほうが好きなんだけど? 諦めましょう。Rust は昔 Result 型も Either
型をもっていましたが、エラーハンド リングという⽤途で使われるはずの Either はユーザーにはほぼ使われず、 Result 型のみが残ったという経緯があります。 まとめた: https://zenn.dev/helloyuki/scraps/e5af11fecac719 ちなみに Scala と Rust を⾏き来すると、エラーを⼊れる側を間違えてよく怒られます。 19 -
値の所有者がだんだん移っていく例 ⼀度変数に束縛した値を、別の変数に再代⼊するとまずは起こります。 // 下記は main 関数内に書いているイメージ。 // 変数 `s` に値を紐付けた。
let s: String = "this is a value".to_string(); // 以下の⾏で、`s` の値は `t` に所有権が移る。 let t = s; // `s` はもう使⽤できないので、コンパイルエラー。 println!("{}", s); 23 -
値の所有者がだんだん移っていく例 関数に値を⼊れても、同様に所有権の移動が起こります。 fn print_something(s: String) { println!("{}", s); } //
s はここで解放される。 // 下記は main 関数に書いてあるイメージ。 // 変数 `s` に値を紐付けた。 let s = "this is a value".to_string(); // 以下の⾏で、`s` の値の所有権は `print_something` 関数に移る。 print_something(s); // `s` はもう使⽤できないので、コンパイルエラー。 println!("{}", s); 27 -
コピーセマンティクス 先ほどまで紹介したものを「ムーブセマンティクス」といいます。 ⼀⽅で、数値型など軽めのものまでムーブしていては、正直だるいです。 i32 や f64 などのプリミティブ型は Copy トレイトが実装されていて、⾃動でコピ ーを⾏ってくれます。
これをコピーセマンティクスと呼びます。 ⾃分で Copy トレイトを実装すれば、独⾃のデータ型に対してコピーセマンティクスを 適⽤できます。 28 -
コピーセマンティクスの例 下記はコピーセマンティクスなので、裏で⾃動でコピーが⾛ります。 fn main() { let a: i32 = 1;
let b = a; // この時点で、a は b にコピーされる。 println!("{}", a); // ムーブセマンティクスならコンパイルエラーだが、通る。 } 29 -
先ほどの例を完全に動くようにしてみる あまり旨味を感じられないが、変数に所有権を移していた例のコンパイルを通るように します。 // 下記は main 関数内に書いているイメージ。 // 変数 `s`
に値を紐付けた。 let s = "this is a value".to_string(); // 以下の⾏では、`t` は `s` を借⽤する。 let t = &s; // `s` の所有権はまだなくなっていないので、標準出⼒できる。 println!("{}", s); 31 -
先ほどの例を完全に動くようにしてみる こちらはよくやる、関数に所有権を移してしまっていた例。 仮引数は s の参照を受け取るようにし、実引数は s の借⽤を渡すようにします。 // `s` は参照を受け取る。
fn print_something(s: &str) { println!("{}", s); } // 下記は main 関数に書いてあるイメージ。 // 変数 `s` に値を紐付けた。 let s = "this is a value".to_string(); // 以下の⾏で、`s` の値を借⽤して渡す。 print_something(&s); // `s` は解放されていないので、標準出⼒できる。 println!("{}", s); 32 -
エクササイズ 1 下記のコードのコンパイルエラーを通せるようにしてみましょう。 struct User { tag: i32 } impl
User { fn new(num: i32) -> User { User { tag: num } } fn print_tag(self) -> i32 { self.tag } } fn main() { let mut user = User::new(1); assert_eq!(user.print_tag(), 1); user.tag = 2; assert_eq!(user.print_tag(), 2); } 33 -
解説: エクササイズ 1 main 関数内で束縛している user の所有権が問題になっている。 1 回⽬の assert_eq!
にて、 print_tag メソッドが呼ばれるが、これに所有権が移 る。 次の⾏に⾏くまでに所有権が解放されてしまう。 print_tag メソッドは借⽤を利⽤するように修正する。 34 -
解答: エクササイズ 2 struct User { tag: i32 } impl
User { fn new(num: i32) -> User { User { tag: num } } fn print_tag(&self) -> i32 { self.tag } } fn main() { let mut user = User::new(1); assert_eq!(user.print_tag(), 1); user.tag = 2; assert_eq!(user.print_tag(), 2); } 35 -
エクササイズ 2 ちょっと難問。時間がなかったら⾶ばすかも。 下記のコードのコンパイルエラーを通せるようにしてみましょう。 fn main() { let mut list
= vec![]; add_elem(list, 1); add_elem(list, 2); add_elem(list, 3); assert_eq!(list, vec![1, 2, 3]); } fn add_elem(mut target: Vec<i32>, elem: i32) { target.push(elem); } 36 -
解説: エクササイズ 2 add_elem が問題。呼び出すと list の所有権が add_elem 関数に移る。 2
回⽬以降は呼び出せない。 なので、 add_elem が受け取るリストは &mut にする必要がある。 可変参照を関数の実引数として渡すには、 &mut を先頭につける必要がある。 37 -
解答: エクササイズ 2 fn main() { let mut list =
vec![]; add_elem(&mut list, 1); add_elem(&mut list, 2); add_elem(&mut list, 3); assert_eq!(list, vec![1, 2, 3]); } fn add_elem(target: &mut Vec<i32>, elem: i32) { target.push(elem); } 38 -
ライフタイムはブロック(スコープ)単位で識別される fn main() { let r; // r ----------------------- {
// | // x のスコープはこのブロック内まで。 // | let x = 1; // | x -------------- r = &x; // | | } // x が解放される // | + -------------- // | // * は参照外し。 // | // &x で &i32 型だったが、それを i32 型にしている。 // この時点で x は破棄されている // | // が、x を使おうとしている // | println!("{}", *r); // | <-- ここで使⽤ } // + ------------------------ 40 -
ライフタイムはブロック(スコープ)単位で識別される fn main() { let r; // r --------------------- {
// | let x = 1; // | x ------------- r = &x; // | | // ブロック内で print するようにしたので、// | | // x が残った状態で使⽤できている。 // | | println!("{}", *r); // | <- 使⽤ + ------------- } // | } // + --------------------- 41 -
ライフタイム識別⼦ 'a , 'b といったように書かれます。("tick a", "tick b" と読みます) //
関数の場合 fn lifetime_string<'a>() -> &'a str { "lifetime string" } // 構造体の場合 struct LifetimeString<'a> { value: &'a str } 42 -
書き⽅: 参照を引数として渡したとき 普段は省略されていますが、実は関数を何も省略せずに定義すると下記のようになりま す。 関数に渡した s という仮引数のライフタイムと、返り値の &str のライフタイムが同じ になるということです。
// 下記は fn g(p: i32) { ... } とも書ける fn g<'a>(p: &'a i32) { ... } let x = 10; g(x); 43 -
書き⽅: 構造体の構造体 さらに新しい構造体 T を定義し、 S をもたせたいとします。 その際には T のライフタイム識別⼦と紐付けておく必要があります。
struct T<'a> { s: S<'a> } 45 -
エクササイズ 1 下記コードのコンパイルを通してみましょう。 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 result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is {}", result); } 47 -
解説: エクササイズ 1 string1 は {} で囲まれたブロック外まで有効。 string2 は {}
で囲まれたブロック内でのみ有効。 println! した時点では、 string2 のライフタイムが切れてしまっている。 48 -
解説: エクササイズ 1 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()); } // `string2` のライフタイムがここで切れてしまう。 // ⽚⽅のライフタイムが切れている `result` を使⽤しようとするので、コンパイルエラー // `string2` がダングリングポインタになってしまっている。 println!("The longest string is {}", result); } 49 -
解説: エクササイズ 1 fn main() { let string1 = String::from("long
string is long"); // string1 -------------- let result; // | { // | let string2 = String::from("xyz"); // | string 2 ---- result = longest(string1.as_str(), string2.as_str()); // | | } // | + ----------- // | println!("The longest string is {}", result); // + -------------------- } 50 -
解答: エクササイズ 1 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 result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); println!("The longest string is {}", result); } } 51 -
解説: エクササイズ 1 fn main() { let string1 = String::from("long
string is long"); // string1 -------------- let result; // | { // | let string2 = String::from("xyz"); // | string 2 ---- result = longest(string1.as_str(), string2.as_str()); // | | println!("The longest string is {}", result); // | | } // | +------------ } // + -------------------- 52 -
エクササイズ 2 下記コードに適切にライフタイム識別⼦を付与し、コンパイルを通してみましょう。 struct User { id: UserId, user_name: UserName
} impl User { fn new(user_name: &str) -> Self { User { id: UserId(1), user_name: UserName(user_name), } } } struct UserId(i32); struct UserName(&str); fn main() { let user = User::new("namae"); assert_eq!(user.user_name.0, "namae"); } 53 -
解答: エクササイズ 2 struct User<'a> { id: UserId, user_name: UserName<'a>
} impl<'a> User<'a> { fn new(user_name: &'a str) -> Self { User { id: UserId(1), user_name: UserName(user_name), } } } struct UserId(i32); struct UserName<'a>(&'a str); fn main() { let user = User::new("namae"); assert_eq!(user.user_name.0, "namae"); } 55