Open18

Rust と戯れる

きむそんきむそん

型システムについて学ぶ

きむそんきむそん

struct: 構造体の型を定義する

pub struct User {
    pub name: String,
    pub age: u32
}

pub struct NewsArticle {
    pub content: String,
    pub author: User
}

struct で構造体(の型)は作れる、メソッドの型を書くことはできない

きむそんきむそん

trait: 構造体に生やすメソッドの型を定義する

pub trait Summary {
    fn summarize(&self) -> String;
}

ただあくまで振る舞いの型定義なので、構造体に紐づくものではない。

きむそんきむそん

impl: メソッドの実態を実装する

impl はこれ以外にも用途がありそう(?)だけど、とりあえず構造体に紐付いたメソッドを実装するのに使える

// 普通にメソッド生やす
impl NewsArticle {
    fn hoge(&self) -> String {
        String::from("hoge")
    }
}

// trait (メソッドの型定義) に従って生やす
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}", self.content)
    }
}

たぶん、実装なしに構造体にメソッドの型定義だけ生やすことはできないっぽい
つまり、TS で言うところの

type Foo = {
  name: string,
  method: () => string
}

なる型は定義できない、trait ベースにせよそうでないにせよ実装が伴う必要がある

あと、TS から見ると、型宣言が複数のブロック(?)にまたがるのはとても違和感があって named import (?)してもちゃんとメソッドの型生えててくれるのかなとか疑問だったけどどうやら大丈夫らしい(まあ、そりゃそうだよね)

きむそんきむそん

構造体の実態を作る

let user = User {
   name: String::from("kaito"),
   age: 20
};

文字列は &strString の2種類があってややこしい、あとでちゃんと調べるけどとりあえず基本は String::from("もじれつ〜") しておけば良さそう。"もじれつ〜".to_string() でも同じっぽいけど差分がいまいちわかってない

きむそんきむそん

交差型とか共用体型とか

ググってもあまり情報がヒットしてくれない

高度な型 - The Rust Programming Language 日本語版
この辺?Result 型の話があるから少なくとも共用体型はあると思うんだけど、、

https://doc.rust-lang.org/std/result/enum.Result.html

なるほど、Result は enum でできてるらしい

https://doc.rust-jp.rs/rust-by-example-ja/custom_types/enum.html

pub enum MyResult<T, E> {
    MyOk(T),
    MyErr(E),
}

fn f(arg: MyResult<String, String>) -> () {
    let data = match arg {
        MyResult::MyOk(ok_data)  => ok_data,
        MyResult::MyErr(ng_data) => ng_data,
    };
    println!("{}", data);
}

おお、elm っぽい。良き

冗長なので、ブロック内で use して使うこともできる

pub enum MyResult<T, E> {
    MyOk(T),
    MyErr(E),
}

fn f(arg: MyResult<String, String>) -> () {
    use MyResult::{MyOk, MyErr};

    let data = match arg {
        MyOk(ok_data)  => ok_data,
        MyErr(ng_data) => ng_data,
    };
    println!("{}", data);
}

交差型はないっぽい、メソッドに関しては複数のトレイトを impl すれば良い

きむそんきむそん

ジェネリクス

ジェネリクス - Rust By Example 日本語版

fn foo<T>(arg: T) { ... }

ごく一般的な書き方

対象は?見出し的には、関数・メソッド・トレイトはできそう。あと上で enum でも使った。構造体が見当たらないなーと思ったけど、type エイリアスでやれば良いのかな

pub struct Foo<T> {
    pub name: T
}

コンパイルエラーでないからこれはOKなのか

let foo = Foo { name: String::from("kaito") };  // OK
let foo2 = Foo<String> { name: String::from("kaito") };  // NG

へー、明示は逆にできないんだ。とりあえず構造体でもジェネリクス使えることがわかった

きむそんきむそん

ジェネリクスに成約をつける: ジェネリック境界

ジェネリック境界 - Rust By Example 日本語版

TS で言う

function foo<T extends string>(arg: T): void {}

fn foo<T: String>(arg: T) -> () {}

ってことらしい(細かい違いはあるだろうけど)

※ void は () で表現する模様

Where句 - Rust By Example 日本語版

where でもジェネリック境界をかけるらしいが、ts と近い前の構文のが読みやすいのでとりあえず知ってるだけで良いや

きむそんきむそん

構造体を表示したい

println!("{}", var)

で構造体の表示を試みると、std::fmt::{Display} トレイトが実装されてないって怒られる。さすがに全ての構造体で表示メソッド実装するのきつくない?

Rustで構造体とか配列をprintln!で一発で出す方法 - Qiita

#[derive(Debug)] っていうデコレータ(?)チックなものを構造体の上に書いてやると

println!("{:?}", var)

で表示できるらしい → できた

derive ってなに? 見出し分け

きむそんきむそん

derive is 何

継承(Derive) - Rust By Example 日本語版

コンパイラには、[#derive]アトリビュートを用いることで型に対して特定のトレイトの標準的な実装を提供する機能があります。

なるほど、良き!
継承とは言ってるけど、クラスじゃないのであくまで「引数に渡したトレイトの組み込みの簡易的な実装をしてくれるもの」という立ち位置

Hello, world!
User { name: "kaito", age: 20 }
NewsArticle { content: "Introduction for type-predicates-generator", author: User { name: "kaito", age: 20 } }

で、アトリビュート is 何。見出し分ける

きむそんきむそん

アトリビュート

アトリビュート - Rust By Example 日本語版

アトリビュートはモジュール、クレート、要素に対するメタデータです。以下がその使用目的です。

基本的には標準モジュールとか、ライブラリとかが指定する形でメタデータ渡すようにすれば良いかな。ライブラリ作りたいとか思うまでは深堀らなくて良さそう(初学者が突っ込むようなところじゃなさそうだし)

きむそんきむそん
きむそんきむそん

難しい

  • ヒープに入れるデータ(実行時まで確定しないようなもの)は、一般的には GC がメモリ開放するか、開発者がメモリ開放するかだけど、Rust だと値の所有権をもつ人(変数)を1人にしておいてスコープが抜けた時に開放する
  • 値を他の変数(引数含む)に渡すときにいちいち所有権渡すと使えなくなって大変なので → 参照だけ渡す(=借用)で解決する
きむそんきむそん

と言いつつ、可変な参照もあって

-change(&s);
+change(&mut s);

で可変な参照を渡せる。あくまで所有権はメモリ解放のためだから別に中で変更されるのはOKってことね

ただ可変な参照は同時に2個作ることは競合を防ぐためにできなくなっている。まあ同じ参照を2箇所から書き換えたらやばいね(所有権の話からはずれてる気もする)

ふう!さらに不変な参照をしている間は、可変な参照をすることはできません。不変参照の使用者は、 それ以降に値が突然変わることなんて予想してません!

良いっすね

きむそんきむそん

メモ

きむそんきむそん

三項演算子ないらしい、代わりに if が statement ではなく expression なので同じように使える

let foo = if selected { "selected" } : { "not_selected" };

ブロックは必須らしい

きむそんきむそん

クロージャ

    use_effect_with_deps(
        move |state| {
            LocalStorage::set(KEY, &state.clone().entries).expect("failed to set");
            || ()
        },
        state.clone(),
    );

この辺の縦棒よく見るけど意味わからんてなってたので調べる

https://doc.rust-jp.rs/book-ja/ch13-00-functional-features.html

クロージャ君

Rustのクロージャは、変数に保存したり、引数として他の関数に渡すことのできる匿名関数です。
関数と異なり、呼び出されたスコープの値をクロージャは、キャプチャすることができます。

  • 高階関数で引数に渡すときとかに使う(関数が渡せないのかはよくわからないが、関数に置き換えてみたら宣言した変数にアクセスできなかった)