Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rustハンズオン第3回 基礎文法編

Yuki Toyoda
May 15, 2021
830

Rustハンズオン第3回 基礎文法編

2021/03/17 に開催した社内向け Rust ハンズオンの資料です。

Yuki Toyoda

May 15, 2021
Tweet

Transcript

  1. 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
  2. Result : エラーハンドリングを⾏う エラー型⾃体も enum で記述することが多いです。 実運⽤では anyhow と thiserror

    というクレートを組み合わせて作ります。 enum CalcError { // ゼロ除算を⾏った場合に返すエラー DividedByZero, // 負の数が⼊っていた場合に返すエラー。中にその数値を⼊れる。 DetectedNegative(i32), } 10
  3. 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
  4. ファイルパスは実⾏時の引数から渡せるようにする さきほどの例では、ファイルパスはハードコーディングでした。実⾏時に引数で渡せる ようにすると、柔軟になるでしょう。 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
  5. ⼊れ⼦はちょっと読みにくいので、関数を出す ネストが発⽣しました。好みの問題ではありますが、ネストは⼀般に読みにくさを増し ます。 ファイルの読み込み処理を関数に切り出しましょう。 ところで、 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
  6. 「ない」を⽰す 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
  7. 急いでいるときに使える 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
  8. 実⾏してみよう 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
  9. なぜ Result ? Either のほうが好きなんだけど? 諦めましょう。Rust は昔 Result 型も Either

    型をもっていましたが、エラーハンド リングという⽤途で使われるはずの Either はユーザーにはほぼ使われず、 Result 型のみが残ったという経緯があります。 まとめた: https://zenn.dev/helloyuki/scraps/e5af11fecac719 ちなみに Scala と Rust を⾏き来すると、エラーを⼊れる側を間違えてよく怒られます。 19
  10. 値の所有者がだんだん移っていく例 ⼀度変数に束縛した値を、別の変数に再代⼊するとまずは起こります。 // 下記は main 関数内に書いているイメージ。 // 変数 `s` に値を紐付けた。

    let s: String = "this is a value".to_string(); // 以下の⾏で、`s` の値は `t` に所有権が移る。 let t = s; // `s` はもう使⽤できないので、コンパイルエラー。 println!("{}", s); 23
  11. 24

  12. 25

  13. 26

  14. 値の所有者がだんだん移っていく例 関数に値を⼊れても、同様に所有権の移動が起こります。 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
  15. コピーセマンティクスの例 下記はコピーセマンティクスなので、裏で⾃動でコピーが⾛ります。 fn main() { let a: i32 = 1;

    let b = a; // この時点で、a は b にコピーされる。 println!("{}", a); // ムーブセマンティクスならコンパイルエラーだが、通る。 } 29
  16. 先ほどの例を完全に動くようにしてみる あまり旨味を感じられないが、変数に所有権を移していた例のコンパイルを通るように します。 // 下記は main 関数内に書いているイメージ。 // 変数 `s`

    に値を紐付けた。 let s = "this is a value".to_string(); // 以下の⾏では、`t` は `s` を借⽤する。 let t = &s; // `s` の所有権はまだなくなっていないので、標準出⼒できる。 println!("{}", s); 31
  17. 先ほどの例を完全に動くようにしてみる こちらはよくやる、関数に所有権を移してしまっていた例。 仮引数は 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
  18. エクササイズ 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
  19. 解説: エクササイズ 1 main 関数内で束縛している user の所有権が問題になっている。 1 回⽬の assert_eq!

    にて、 print_tag メソッドが呼ばれるが、これに所有権が移 る。 次の⾏に⾏くまでに所有権が解放されてしまう。 print_tag メソッドは借⽤を利⽤するように修正する。 34
  20. 解答: エクササイズ 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
  21. エクササイズ 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
  22. 解説: エクササイズ 2 add_elem が問題。呼び出すと list の所有権が add_elem 関数に移る。 2

    回⽬以降は呼び出せない。 なので、 add_elem が受け取るリストは &mut にする必要がある。 可変参照を関数の実引数として渡すには、 &mut を先頭につける必要がある。 37
  23. 解答: エクササイズ 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
  24. ライフタイムはブロック(スコープ)単位で識別される fn main() { let r; // r ----------------------- {

    // | // x のスコープはこのブロック内まで。 // | let x = 1; // | x -------------- r = &x; // | | } // x が解放される // | + -------------- // | // * は参照外し。 // | // &x で &i32 型だったが、それを i32 型にしている。 // この時点で x は破棄されている // | // が、x を使おうとしている // | println!("{}", *r); // | <-- ここで使⽤ } // + ------------------------ 40
  25. ライフタイムはブロック(スコープ)単位で識別される fn main() { let r; // r --------------------- {

    // | let x = 1; // | x ------------- r = &x; // | | // ブロック内で print するようにしたので、// | | // x が残った状態で使⽤できている。 // | | println!("{}", *r); // | <- 使⽤ + ------------- } // | } // + --------------------- 41
  26. ライフタイム識別⼦ 'a , 'b といったように書かれます。("tick a", "tick b" と読みます) //

    関数の場合 fn lifetime_string<'a>() -> &'a str { "lifetime string" } // 構造体の場合 struct LifetimeString<'a> { value: &'a str } 42
  27. エクササイズ 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
  28. 解説: エクササイズ 1 string1 は {} で囲まれたブロック外まで有効。 string2 は {}

    で囲まれたブロック内でのみ有効。 println! した時点では、 string2 のライフタイムが切れてしまっている。 48
  29. 解説: エクササイズ 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
  30. 解説: エクササイズ 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
  31. 解答: エクササイズ 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
  32. 解説: エクササイズ 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
  33. エクササイズ 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
  34. 解答: エクササイズ 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