Rust勉強会(new)

ワイ史上最強の言語! Rustをちゃんと理解してあげる

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

今日のお品書き

はじめに

  • 注意事項
  • 参考リンク

telumoが思うRustの素晴らしさ

  • オープンソース
  • フツーに書ける
  • パフォーマンスが高い!
  • テストしやすい!!!(これ、重要)
  • クロスコンパイル
  • モダン〜OptionやResult〜
  • フロントも書ける!(WebAssembly)
  • マルチパラダイム
  • マクロが書ける!
  • アトリビュートが便利!

所有権/借用/ライフタイム
難しいと言われるRustの概念について少しだけ。

実装編(Installから〇〇アプリまで)

5. おわりに
まとめます。


はじめに

注意事項

  • 現時点でのRustのバージョンは1.43.1です。
  • 主催者はRust初心者です。
  • 資料はあくまでも補助資料のつもりで。
  • 言うまでもありませんが、言語は手段です。「この言語さえ勉強すれば大丈夫!」という魔法の様な言語は存在しませんので、用法・用量を守って正しくお使いください。
  • とはいえRustは素晴らしい言語ですので、「Rustが好きすぎる病」が発症する可能性があります。ご注意ください。
  • 質問はいっぱいしてください!
  • 普段はこんなにしっかり資料を作りません。(今回はちょっと自己研鑽のためにも資料を作ってみました。)

参考リンク

リンク 内容
Rustプログラミング言語 Rustの公式ホームページ。シンプルで分かりやすい。
The Book 文法が一通り書かれている。最初から全部読もうとすると大変かも。
Rust By Example コードで説明するタイプ。説明は少なめ。練習問題もあり。
標準ライブラリ Rust標準ライブラリガイド。最初から読む物じゃなくて、参照用。
CARGOブック RustのパッケージマネージャーCargoの説明。参照用。
RUSTDOCブック ドキュメントの作成方法や書き方の説明。参照用。
RUSTCブック RustのコンパイラRustcの説明。参照用。
Rustの日本語ドキュメント集 日本語ドキュメント。若干古いものあるので注意。
Awesome Rust Rustの便利ツールがリスト化されている。

Rustの素晴らしさ => オープンソース

RustはMozillaが作った言語。
Mozillaらしくオープンソース(github)
RFCの議論さえもYouTubeに公開している!↓

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

GoとかSwiftとか最近人気な言語はあるやん?
でも、結局、GoogleとかAppleの言語なんよ。
全てのソースコードも確認できて、リクエストも出せる。最高。

こういうのも「最も愛されている」理由かもしれない。

Rustの素晴らしさ => フツーに書ける

Rustは難しくない。
C/C++, Java, JavaScriptとかを書ける人は普通にRustも書けるよ。

https://github.com/telumo/giflet/blob/master/src/main.rs

Rustの素晴らしさ => パフォーマンスが高い!

速い
Rustは最も速いプログラミング言語の1つ。
CやC++などと同様の速さらしい。

理由の一つにGC(ガベージコレクタ)がないことがある。

メモリーリークを防ぐ戦略
  1. 自力
    • C, C++など
    • メリット
      • ちゃんと実装すれば、高速。
    • デメリット
      • ちゃんと実装しなければ、死ぬ。
  2. ガベージコレクション(GC, ガベコレ)
    • ほぼ全ての言語が装備(Java, PHP, Python, Ruby, Go, Haskell
    • 実装方法
      • 参照カウント
      • マーク・アンド・スイープ
    • メリット
      • メモリ管理を意識する必要がない。
    • デメリット
      • 自力戦略に比べると速度は劣る。
      • 場合によってはStop the world?(現代のGCでは改善されているかも。)
  3. 所有権
    • Rust
    • メリット
      • CやC++と同じくらい高速
    • デメリット
      • 学習コスト?(ボローチェッカーで管理してくれるものの)

メモリ安全
あとで話す所有権のおかげでメモリ安全!
unsafeとか使わなければね。)

ゼロコスト抽象化(静的ディスパッチ)

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用のバイナル作る場合は、基本的にはこれだけ。

cross build --target x86_64-pc-windows-gnu

参考
https://github.com/telumo/giflet/releases

りんごで例えると?

日本人の味覚に合うりんごがアメリカで作れるらしいよ。

Rustの素晴らしさ => モダン〜OptionやResult〜

まず、nullがない。全て型で理解する。

def f(x=None): if x is None: # こう言うの書きたくないよね。 return 0 else: return x

ResultOptionを理解する

enum Result<T, E> { Ok<T>, Err<E>, } enum Option<T> { Some<T>, None }
// 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 }
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)); }
りんごで例えると?

Rustの素晴らしさ => フロントも書ける!(WebAssembly)

プログラミング言語やライブラリの名前ではなく、ブラウザでプログラムを高速実行するための、
「ブラウザ上で動くバイナリコードの新しいフォーマット(仕様)」
である。
Google, Microsoft, Mozzila, Appleによって仕様が策定され開発が進められている。

https://qiita.com/ShuntaShirai/items/3ac92412720789576f22

Rust, WebAssembly, and the future of Serverless by Steve Klabnik

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

これを見るとWebAssemblyが今後のDockerの代わりになるかもしれない。。。!

yew

これは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

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の素晴らしさ => マルチパラダイム

オブジェクト思考

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 // 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 -} 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 match e { 0 => 1, t @ 2 => t + 1, n if n < 10 => 3, _ => 4 }
{- Haskell -} case e of 0 -> 1 t @ 2 -> t + 1 n | n < 10 -> 3 _ -> 4

代数的データ型

// Rust enum Option<T> { None, Some(T) } // x : Option<t> match x { None => false, Some(_) => true }
{- Haskell -} data Maybe a = Nothing | Just a {- x : Maybe t -} case x of Nothing -> False Just _ -> True

トレイトと型クラス

// 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 -} 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

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の各値は、所有者と呼ばれる変数と対応している。
  • いかなる時も所有者は一つである。
  • 所有者がスコープから外れたら、値は破棄される。

突然ですが、これコンパイル通ると思いますか?

fn main() { let x = 5; let y = x; println!("x = {}, y = {}", x, y); }

これは?

let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1);

これはどうでしょう?

// 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 }

これなら?

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() }

借用

じゃーこれは?

fn main() { let s = String::from("hello"); change(&s); } fn change(some_string: &String) { some_string.push_str(", world"); }

これなら?

fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); }
let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s;
let mut s = String::from("hello"); { let r1 = &mut s; } // r1はここでスコープを抜けるので、問題なく新しい参照を作ることができる let r2 = &mut s;
let mut s = String::from("hello"); let r1 = &s; // 問題なし let r2 = &s; // 問題なし let r3 = &mut s; // 大問題!

結局ボローチェッカーを信じれば大丈夫!

実装編(Installから〇〇アプリまで)

0. インストール

まずは、rustupコマンドを取得する。
これでcargoコマンドも一緒に入る。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

最新のRustをインストールしておく。

rustup update

1. VSCode + RLSを使う

やっぱりVSCode最強説。軽いし、いろんなプラグインがある。
TODO: VSCodeプラグイン作る勉強会やりたい。

RLSを入れると無敵。
一つだけ、Rustパスを設定するのを忘れずに。

/Users/hikaru/.cargo/bin/rustup

2. TDD

3. watchを使う

cargo install cargo-watch

4. clippy

rustup component add clippy cargo clippy

5. 何作る?

おわりに

  • Rustはモダンな言語機能と低水準プログラミングが両立できる。
  • とにかくテストがしやすい!!
  • WebAssemblyが作りやすい。