--- title: Rust勉強会new tags: Rust, WebAssembly, TDD description: Rust勉強会 lang: ja image: https://www.rust-lang.org/static/images/rust-logo-blk.svg slideOptions: transition: slide --- # Rust勉強会(new) ワイ史上最強の言語! Rustをちゃんと理解してあげる ![Rust Logo](https://www.rust-lang.org/static/images/rust-logo-blk.svg) ## 今日のお品書き **はじめに** - 注意事項 - 参考リンク **telumoが思うRustの素晴らしさ** - オープンソース - フツーに書ける - パフォーマンスが高い! - テストしやすい!!!(これ、重要) - クロスコンパイル - モダン〜OptionやResult〜 - フロントも書ける!(WebAssembly) - マルチパラダイム - マクロが書ける! - アトリビュートが便利! **所有権/借用/ライフタイム** 難しいと言われるRustの概念について少しだけ。 **実装編(Installから〇〇アプリまで)** **5. おわりに** まとめます。 --- ## はじめに ### 注意事項 - 現時点でのRustのバージョンは1.43.1です。 - 主催者はRust初心者です。 - 資料はあくまでも補助資料のつもりで。 - 言うまでもありませんが、言語は手段です。「この言語さえ勉強すれば大丈夫!」という魔法の様な言語は***存在しません***ので、用法・用量を守って正しくお使いください。 - とはいえRustは素晴らしい言語ですので、「**Rustが好きすぎる病**」が発症する可能性があります。ご注意ください。 - 質問はいっぱいしてください! - 普段はこんなにしっかり資料を作りません。(今回はちょっと自己研鑽のためにも資料を作ってみました。) ### 参考リンク | リンク | 内容 | | -------- | -------- | | [Rustプログラミング言語](https://www.rust-lang.org/ja) | Rustの公式ホームページ。シンプルで分かりやすい。| | [The Book](https://doc.rust-lang.org/book/) | 文法が一通り書かれている。最初から全部読もうとすると大変かも。| | [Rust By Example](https://doc.rust-lang.org/book/) | コードで説明するタイプ。説明は少なめ。練習問題もあり。| | [標準ライブラリ](https://doc.rust-lang.org/std/index.html) | Rust標準ライブラリガイド。最初から読む物じゃなくて、参照用。 | | [CARGOブック](https://doc.rust-lang.org/cargo/index.html) | RustのパッケージマネージャーCargoの説明。参照用。 | | [RUSTDOCブック](https://doc.rust-lang.org/rustdoc/index.html) | ドキュメントの作成方法や書き方の説明。参照用。 | | [RUSTCブック](https://doc.rust-lang.org/rustc/index.html) | RustのコンパイラRustcの説明。参照用。 | | [Rustの日本語ドキュメント集](https://doc.rust-jp.rs/) | 日本語ドキュメント。若干古いものあるので注意。 | | [Awesome Rust](https://github.com/rust-unofficial/awesome-rust#game-development) | Rustの便利ツールがリスト化されている。 | --- ## Rustの素晴らしさ => オープンソース RustはMozillaが作った言語。 Mozillaらしく[オープンソース(github)](https://github.com/rust-lang/rust)。 RFCの議論さえもYouTubeに公開している!↓ {%youtube 41pnkSS6MzY %} GoとかSwiftとか最近人気な言語はあるやん? でも、結局、GoogleとかAppleの言語なんよ。 全てのソースコードも確認できて、リクエストも出せる。最高。 こういうのも「[最も愛されている](https://codezine.jp/article/detail/12350)」理由かもしれない。 ## Rustの素晴らしさ => フツーに書ける Rustは難しくない。 C/C++, Java, JavaScriptとかを書ける人は普通にRustも書けるよ。 https://github.com/telumo/giflet/blob/master/src/main.rs ## Rustの素晴らしさ => パフォーマンスが高い! **速い** Rustは最も速いプログラミング言語の1つ。 CやC++などと同様の速さらしい。 理由の一つにGC(ガベージコレクタ)がないことがある。 :::spoiler メモリーリークを防ぐ戦略 1. 自力 - C, C++など - メリット - ちゃんと実装すれば、高速。 - デメリット - ちゃんと実装しなければ、死ぬ。 2. ガベージコレクション(GC, ガベコレ) - ほぼ全ての言語が装備(Java, PHP, Python, Ruby, Go, Haskell...) - 実装方法 - 参照カウント - マーク・アンド・スイープ - ... - メリット - メモリ管理を意識する必要がない。 - デメリット - 自力戦略に比べると速度は劣る。 - 場合によってはStop the world?(現代のGCでは改善されているかも。) 3. 所有権 - Rust - メリット - CやC++と同じくらい高速 - デメリット - 学習コスト?(ボローチェッカーで管理してくれるものの) ::: </br> **メモリ安全** あとで話す所有権のおかげでメモリ安全! (`unsafe`とか使わなければね。) **ゼロコスト抽象化(静的ディスパッチ)** ```rust= use std::fmt::Debug; fn main() { fn static_dispatch<T: Debug>(x: T) { println! {"{:?}", x}; } static_dispatch(2020); static_dispatch("thursday"); #[derive(Debug)] struct Point { x: i32, // xは32ビット符号あり数値として指定 y: String, // yは文字列として指定 } let points = Point { x: 2020, y: "thursday".to_string() }; static_dispatch(points); } ``` ## Rustの素晴らしさ => テストしやすい!!! `#[test]`って書くだけ https://github.com/telumo/giflet/blob/master/src/cli.rs#L49 ## Rustの素晴らしさ => クロスコンパイル linuxやmacOS上でWindows用のバイナル作る場合は、基本的にはこれだけ。 ```shell= cross build --target x86_64-pc-windows-gnu ``` 参考 https://github.com/telumo/giflet/releases :::spoiler りんごで例えると? 日本人の味覚に合うりんごがアメリカで作れるらしいよ。 ::: ## Rustの素晴らしさ => モダン〜OptionやResult〜 まず、`null`がない。全て型で理解する。 ```python= def f(x=None): if x is None: # こう言うの書きたくないよね。 return 0 else: return x ``` `Result`と`Option`を理解する ```rust= enum Result<T, E> { Ok<T>, Err<E>, } enum Option<T> { Some<T>, None } ``` ```rust= // Optionの便利メソッド fn main(){ // Some(v): 値がある場合 // None: 値が無い場合 let some_value: Option<usize> = Some(1); let none_value: Option<usize> = None; // Optionの中身はパターンマッチで取り出す match some_value { Some(v) => println!("some value = {}", v), None => println!("none value"), }; // Someの場合だけ処理したいならif letの方が便利 if let Some(v) = some_value { println!("some value = {}", v); } // 比較もできる // ただし、Option<T>のT型がEqやOrdトレイトを実装している必要がある assert!(some_value != none_value); // Option<T>には便利メソッドがいろいろある // Some(v) or Noneを判定 assert!(some_value.is_some()); assert!(none_value.is_none()); // unwrap: (&self) -> T // Someの場合は中身を返し、Noneの場合はpanicして終了 assert_eq!(some_value.unwrap(), 1); // assert_eq!(none_value.unwrap(), 1); // panicする // expect: (&self, &str) -> T // unwrapと同じだが、panicするときにメッセージを指定できる assert_eq!(some_value.expect("panic!"), 1); // assert_eq!(none_value.expect("panic!"), 1); // panicする // unwrap_or: (&self, T) -> T // unwrapと同じだが、Noneの場合にpanicする代わりに別の値を返す assert_eq!(some_value.unwrap_or(0), 1); assert_eq!(none_value.unwrap_or(0), 0); // unwrap_or_else: (&self, FnOnce() -> T) -> T // unwrap_orと同じだが、Noneの場合の値をClosureで指定する // 遅延評価したいときに使う assert_eq!(some_value.unwrap_or_else(|| 0), 1); assert_eq!(none_value.unwrap_or_else(|| 0), 0); // map: (&self, FnOnce(T) -> U) -> Option<U> // Someの場合は関数を適用し、Noneの場合はNoneまま返す assert_eq!(some_value.map(|v| format!("value is {}", v)), Some("value is 1".to_string())); assert_eq!(none_value.map(|v| format!("value is {}", v)), None); // map_or: (&self, U, FnOnce(T) -> U) -> U // unwrap_orのmap版 assert_eq!(some_value.map_or("value is default".to_string(), |v| format!("value is {}", v)), "value is 1".to_string()); assert_eq!(none_value.map_or("value is default".to_string(), |v| format!("value is {}", v)), "value is default".to_string()); // map_or_else: (&self, FnOnce() -> U, FnOnce(T) -> U) -> U // unwrap_or_elseのmap版 assert_eq!(some_value.map_or_else(|| "value is default".to_string(), |v| format!("value is {}", v)), "value is 1".to_string()); assert_eq!(none_value.map_or_else(|| "value is default".to_string(), |v| format!("value is {}", v)), "value is default".to_string()); // 他にもいろいろあります! // https://doc.rust-lang.org/std/option/enum.Option.html } ``` ```rust= fn main(){ // Ok(v): 処理に成功した場合。返す値が無い場合はUnit型()を返すとよい // Err(e): 処理に失敗した場合。 let ok_value: Result<usize, &'static str> = Ok(1); let er_value: Result<usize, &'static str> = Err("error message"); // Resultの中身はパターンマッチで取り出す match ok_value { Ok(v) => println!("ok value = {}", v), Err(e) => println!("err value = {}", e), }; // どちらか片方の場合だけ処理したいならif letが便利 if let Ok(v) = ok_value { println!("ok value = {}", v); } if let Err(e) = er_value { println!("err value = {}", e); } // 比較もできる // ただし、Reulst<T,E>のTとE両方がEqやOrdトレイトを実装している必要がある assert!(ok_value != er_value); // Result<T,E>には便利メソッドがいろいろある // Ok(v) or Err(e)を判定 assert!(ok_value.is_ok()); assert!(er_value.is_err()); // ok: (&self) // Option型に変換する。Okの場合はSomeに、Errの場合はNoneになる assert_eq!(ok_value.ok(), Some(1)); assert_eq!(er_value.ok(), None); // err: (&self) // okメソッドの逆バージョン。Okの場合はNoneに、Errの場合はSomeになる assert_eq!(ok_value.err(), None); assert_eq!(er_value.err(), Some("error message")); // unwrap<T,E>: (&self) -> T // Okの場合は中身を返し、Errの場合はpanicする assert_eq!(ok_value.unwrap(), 1); // assert_eq!(er_value.unwrap(), 1); // panic // expect<T,E>: (&self, &str) -> T // unwrapと同じだが、panic時のエラーメッセージを指定できる assert_eq!(ok_value.expect("panic"), 1); // assert_eq!(er_value.expect("panic"), 1); // panic // unwrap_or<T,E>: (&self, T) -> T // unwrapと同じだが、panicする代わりに別の値を返す assert_eq!(ok_value.unwrap_or(0), 1); assert_eq!(er_value.unwrap_or(0), 0); // unwrap_or_else<T,E>: (&self, FnOnce(E) -> T) -> T // unwrap_orと同じだが、Errの場合の値をClosureで指定する // 遅延評価したいときに使う // Optionと違ってErrの値を引数に取るので注意 assert_eq!(ok_value.unwrap_or_else(|_e| 0), 1); assert_eq!(er_value.unwrap_or_else(|_e| 0), 0); // unwrap_err<T,E>: (&self) -> E // expect_err<T,E>: (&self, &str) -> E // unwrap, expectのokとerrを逆にしたバージョン。okだとpanicする // assert_eq!(ok_value.unwrap_err(), "error message"); // panic assert_eq!(er_value.unwrap_err(), "error message"); // assert_eq!(ok_value.expect_err("panic"), "error message"); // panic assert_eq!(er_value.expect_err("panic"), "error message"); // map<T,E,U>: (&self, FnOnce(T) -> U) -> Result<U,E> // map_err<T,E,U>: (&self, FnOnce(E) -> U) -> Result<T,U> // Resultの中身を関数を適用してmapする // Okの場合に適用するmapとErrの場合に適用するmap_errがある assert_eq!(ok_value.map(|v| format!("{}", v)), Ok("1".to_string())); assert_eq!(er_value.map(|v| format!("{}", v)), Err("error message")); assert_eq!(ok_value.map_err(|s| s.len()), Ok(1)); assert_eq!(er_value.map_err(|s| s.len()), Err(13)); } ``` :::spoiler りんごで例えると? ::: ## Rustの素晴らしさ => フロントも書ける!(WebAssembly) > プログラミング言語やライブラリの名前ではなく、ブラウザでプログラムを高速実行するための、 「ブラウザ上で動くバイナリコードの新しいフォーマット(仕様)」 である。 Google, Microsoft, Mozzila, Appleによって仕様が策定され開発が進められている。 https://qiita.com/ShuntaShirai/items/3ac92412720789576f22 Rust, WebAssembly, and the future of Serverless by Steve Klabnik {%youtube CMB6AlE1QuI%} これを見るとWebAssemblyが今後のDockerの代わりになるかもしれない。。。! **[yew](https://github.com/yewstack/yew)** これは[Elm](https://guide.elm-lang.jp/) ```elm= import Browser import Html exposing (Html, button, div, text) import Html.Events exposing (onClick) main = Browser.sandbox { init = 0, update = update, view = view } type Msg = Increment | Decrement update msg model = case msg of Increment -> model + 1 Decrement -> model - 1 view model = div [] [ button [ onClick Decrement ] [ text "-" ] , div [] [ text (String.fromInt model) ] , button [ onClick Increment ] [ text "+" ] ] ``` これがRust ```rust= use wasm_bindgen::prelude::*; use yew::prelude::*; struct Model { link: ComponentLink<Self>, value: i64, } enum Msg { AddOne, } impl Component for Model { type Message = Msg; type Properties = (); fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { Self { link, value: 0, } } fn update(&mut self, msg: Self::Message) -> ShouldRender { match msg { Msg::AddOne => self.value += 1 } true } fn change(&mut self, _props: Self::Properties) -> ShouldRender { // Should only return "true" if new properties are different to // previously received properties. // This component has no properties so we will always return "false". false } fn view(&self) -> Html { html! { <div> <button onclick=self.link.callback(|_| Msg::AddOne)>{ "+1" }</button> <p>{ self.value }</p> </div> } } } #[wasm_bindgen(start)] pub fn run_app() { App::<Model>::new().mount_to_body(); } ``` ## Rustの素晴らしさ => マルチパラダイム **オブジェクト思考** ```rust= pub struct AveragedCollection { list: Vec<i32>, average: f64, } impl AveragedCollection { pub fn add(&mut self, value: i32) { self.list.push(value); self.update_average(); } pub fn remove(&mut self) -> Option<i32> { let result = self.list.pop(); match result { Some(value) => { self.update_average(); Some(value) }, None => None, } } pub fn average(&self) -> f64 { self.average } fn update_average(&mut self) { let total: i32 = self.list.iter().sum(); self.average = total as f64 / self.list.len() as f64; } } ``` ※インターフェースに変わる様な`trait`もあるよ。 **関数型** 関数 ```rust= // Rust // f : |int,int| -> int fn f (x:int, y:int) -> int { x + y }; // fact : |int| -> int fn fact (n:int) -> int { if n == 1 { 1 } else { n * fact(n-1) } } ``` ```haskell= {- Haskell -} f :: (Int, Int) -> Int f (x, y) = x + y fact :: Int -> Int fact n = if n = 1 then 1 else n * fact(n-1) ``` パターンマッチ ```rust= // Rust match e { 0 => 1, t @ 2 => t + 1, n if n < 10 => 3, _ => 4 } ``` ```haskell= {- Haskell -} case e of 0 -> 1 t @ 2 -> t + 1 n | n < 10 -> 3 _ -> 4 ``` 代数的データ型 ```rust= // Rust enum Option<T> { None, Some(T) } // x : Option<t> match x { None => false, Some(_) => true } ``` ```haskell= {- Haskell -} data Maybe a = Nothing | Just a {- x : Maybe t -} case x of Nothing -> False Just _ -> True ``` トレイトと型クラス ```rust= // Rust trait Testable { fn test(&self) -> bool } impl Testable for int { fn test(&self) -> int { if *self == 0 { false } else { true } } } fn hello(x:int) -> bool { x.test() } ``` ```haskell= {- Haskell -} class Testable a where test :: a -> Bool instance Testable Int where test n = if n == 0 then False else True hello :: Int -> Bool hello x = test x ``` ## Rustの素晴らしさ => マクロが書ける! https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/macros.html ```rust= macro_rules! write_html { ($w:expr, ) => (()); ($w:expr, $e:tt) => (write!($w, "{}", $e)); ($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) => {{ write!($w, "<{}>", stringify!($tag)); write_html!($w, $($inner)*); write!($w, "</{}>", stringify!($tag)); write_html!($w, $($rest)*); }}; } fn main() { use std::fmt::Write; let mut out = String::new(); write_html!(&mut out, html[ head[title["Macros guide"]] body[h1["Macros are the best!"]] ]); assert_eq!(out, "<html><head><title>Macros guide</title></head>\ <body><h1>Macros are the best!</h1></body></html>"); } ``` ## Rustの素晴らしさ => アトリビュートが便利! **#[derive]が便利!** https://qiita.com/apollo_program/items/2495dda519ae160971ed **serde** https://qiita.com/garkimasera/items/0442ee896403c6b78fb2 ## 所有権/借用/ライフタイム 所有権のルールはこれだけ - Rustの各値は、所有者と呼ばれる変数と対応している。 - いかなる時も所有者は一つである。 - 所有者がスコープから外れたら、値は破棄される。 突然ですが、これコンパイル通ると思いますか? ```rust= fn main() { let x = 5; let y = x; println!("x = {}, y = {}", x, y); } ``` これは? ```rust= let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); ``` これはどうでしょう? ```rust= // rust fn main(){ let a = String::from("aaa"); let b = just_return(a); println!("{}", a); println!("{}", b); } fn just_return(x: String) -> String{ return x } ``` これなら? ```rust= fn main(){ let a = String::from("aaa"); let b = just_return(&a); println!("{}", a); println!("{}", b); } fn just_return(x: &String) -> String{ return x.to_string() } ``` 借用 じゃーこれは? ```rust= fn main() { let s = String::from("hello"); change(&s); } fn change(some_string: &String) { some_string.push_str(", world"); } ``` これなら? ```rust= fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); } ``` ```rust= let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; ``` ```rust= let mut s = String::from("hello"); { let r1 = &mut s; } // r1はここでスコープを抜けるので、問題なく新しい参照を作ることができる let r2 = &mut s; ``` ```rust= let mut s = String::from("hello"); let r1 = &s; // 問題なし let r2 = &s; // 問題なし let r3 = &mut s; // 大問題! ``` 結局ボローチェッカーを信じれば大丈夫! ## 実装編(Installから〇〇アプリまで) ### 0. インストール まずは、`rustup`コマンドを取得する。 これで`cargo`コマンドも一緒に入る。 ```shell= curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` 最新のRustをインストールしておく。 ```shell= rustup update ``` ### 1. VSCode + RLSを使う やっぱりVSCode最強説。軽いし、いろんなプラグインがある。 TODO: VSCodeプラグイン作る勉強会やりたい。 [RLS](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust)を入れると無敵。 一つだけ、Rustパスを設定するのを忘れずに。 ``` /Users/hikaru/.cargo/bin/rustup ``` ### 2. TDD ### 3. [watch](https://github.com/passcod/cargo-watch)を使う ```shell= cargo install cargo-watch ``` ### 4. [clippy](https://github.com/rust-lang/rust-clippy) ```shell= rustup component add clippy cargo clippy ``` ### 5. 何作る? ## おわりに - Rustはモダンな言語機能と低水準プログラミングが両立できる。 - とにかくテストがしやすい!! - WebAssemblyが作りやすい。