Open16

Rust 基礎メモ

kqitokqito

Rustで調べたことをざっとメモるところ

  • 大雑把なメモ
  • 厳密・本質的には意味合いが異なる事があるかも
  • Rust初心者が記述している
kqitokqito

Result型を返す関数

Ok, Errを用いるパターン

fn test(arg: bool) -> Result<String, String> {
    match arg {
        false => Err(String::from("an error message")),
        true => Ok(String::from("ok")),
    }
}

EnumなどをResult型として返す

例えば以下の様にError enum をResult<Hoge, Error>みたいに返すトレイトを実装するケースを想定

pub struct Test;

#[derive(Debug)]
pub enum TestError {
    InvalidSize,
}

impl Test {
    pub fn check_num(num: usize) -> Result<usize, TestError> {
        match num > 10 {
            false => Err(TestError::InvalidSize),
            true => Ok(num),
        }
    }
}
let num = Test::check_num(100).unwrap()

ここでメモっておきたいのはユースケースに応じて [#derive]attributeを利用した各トレイトをEnumに対して実装する必要があるらしいということ

https://doc.rust-jp.rs/rust-by-example-ja/trait/derive.html#継承derive

今回はunwrapなどを利用する為にTestError enumに対してDebug トレイトを実装している

ref

kqitokqito

Where keyword

Where keywordを利用する事でGenericに対して ~~~ の値を渡してねという制約を設ける事ができる。

    pub fn execute<F>(&self, f: F)
    where
        F: FnOnce() + Send + 'static,
    {
    }

これはTypeScriptなどでいうところのGeneric Constraintsと似ている概念かも :think:

ちなみに

https://doc.rust-lang.org/std/keyword.where.html

によると引数に対して直接 Constraints も設ける事ができるが、Where keywordを用いる事で表現力が増すケースがあるらしい

kqitokqito

クロージャ

https://doc.rust-jp.rs/book-ja/ch20-02-multithreaded.html を模写していた時に色々分からない概念があったのでメモ

概要

  • Rustでのクロージャはjsでいうところ無名関数的な立ち位置だったり変数を束縛できること
  • 全てのクロージャはFn, FnMut, FnOnceいずれかのトレイトを実装した値
let fn = |x| { x * 2 }

move keyword

クロージャにキャプチャされた変数全ての所有権を奪わせるmove keywordを利用する事ができる

fn main () {
    let string = String::from("hi.");
    // move keywordの利用でstring 変数の所有権がmoveする
    let echo_string = move || { 
        println!("{}", string);
    };
    
    echo_string();

    // string変数の所有権がないのでここでエラーが発生する
    println!("{}", string);   
}

上記のソースコードの場合、所有権をmoveさせたくないのであればmove keywordを利用しなければコンパイルが通る。("hi."が2回出力される)

キーワード一覧

Rustのキーワードってどう言ったものがあるんだろう :think: と調べたらここら辺で定義されているっぽい

https://github.com/rust-lang/rust/blob/1.58.1/compiler/rustc_span/src/symbol.rs#L23

Fn, FnOnce, FnMutトレイト

クロージャは上記いずれかのトレイトを実装している。
それぞれはクロージャが所有権や借用などをするか否かでトレイトが変化する

Fnトレイト

  • クロージャが所有権や借用のルールに影響がないクロージャはFnとなる。
  • 逆に、所有権などを消費したりmove keywordなどを利用したクロージャはこれに該当しない
  • クロージャを渡す方の制限が高い/クロージャを叩く方の制限は緩い
fn fn_closure<F: Fn()>(func: F) {
    func();
}

fn main() {
    fn_closure(|| println!("hi"))
}

FnOnceトレイト

  • Fnトレイトとは逆に所有権などを消費するクロージャ
  • つまり1度のみだけ呼ばれることを期待するクロージャに関してはFnOnceトレイトが該当する。
  • クロージャを渡す方の制限が緩い/クロージャを叩く方の制限は高い
fn fn_once_closure<F: FnOnce()>(func: F) {
    func();
}

fn main() {
    let message = String::from("hi");
    fn_once_closure(move || println!("{}", message));
}

FnMutトレイト

  • FnOnceのように所有権を消費しないで、変数の借用のみを行うクロージャが該当する。
  • FnMutトレイトを実装したクロージャはFnトレイト同様に複数回実行することが可能

まだちょっとわかってないのがクロージャにmutが必要な理由 => クロージャを叩くのに必要

fn fn_mut_closure<F: FnMut()>(mut func: F) {
    func();
}

fn main() {
    let message = String::from("hi");
    fn_mut_closure(move || println!("{}", &message));
}

Ref

kqitokqito

スマートポインタ

スマートポインタ is 何

Rust特有の概念ではなく一般的な概念(c#などにも存在する)

通常のポインタに加えて特有の機能を持ち合わせたことを指すらしい。

Rustでは以下の様なものがある

  • String: データが常に有効なUTF-8だと保証する
  • Vec: 一般的なベクタ(配列、スライスとも
  • Box: ヒープに値を保存(Boxでなければ基本的にスタックに保存している
  • Rc: 複数の所有権を可能にする

Box struct

Box を利用するとヒープに値を保存することができる。以下の様な用途で利用することが良い

  • 値を参照するスコープがわからない
  • 事前に値のサイズがわからない

など

fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}

Deref trait

Derefトレイトを実装すると参照外し演算子の振る舞いを追加・カスタマイズできる

Rustでもc言語の様なポインタからデータまで参照を辿ることができる

fn main() {
    let number_1 = 100;
    let pointer_1 = &number_1;
    let number_2 = *pointer_1; // 100
}

これに加えてDerefトレイトを利用すると振る舞いを変えることができる

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let number_1 = 100;
    let pointer_1 = MyBox::new(number_1);
    let number_2 = *pointer_1; // 100
}

type Target = Tはジェネリック引数でRust側で以下の様に定義されているために宣言する必要がある (ちょっと読めない部分もあるが...

pub trait Deref {
    /// The resulting type after dereferencing.
    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_diagnostic_item = "deref_target"]
    #[lang = "deref_target"]
    type Target: ?Sized;

    /// Dereferences the value.
    #[must_use]
    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_diagnostic_item = "deref_method"]
    fn deref(&self) -> &Self::Target;
}

この&self.0はstructによる。今回はstruct MyBox<T>(T)だったので&self.0だったが、例えば MyBox structがvalueなんてフィールド値があってそれを返したいのであれば&self.valueになる

Drop trait

Drop traitは値が抜けそうになったときに発生することをカスタマイズできる。

これはスマートポインタという概念から見たときには重要なトレイト。その理由としてはほぼ常にスマートポインタの実装でDrop traitが利用されるから。

例えばBox<T>においてはDropをカスタマイズしてヒープ領域を解放していたりする

struct MyDrop {
    value: String,
}

impl MyDrop {
    fn new(value: String) -> MyDrop {
        MyDrop { value }
    }
}

impl Drop for MyDrop {
    fn drop(&mut self) {
        println!("Drop with {} value", self.value)
    }
}

fn main() {
    let myDrop = MyDrop::new(String::from("MyDrop"));
}

Rc struct

Rc structを利用すると所有権を複数に共有することができる。

例えば以下の様な再起的な型から別の型を生成する際に、所有権が食われてしまって思った様な処理をかけない時がある。


以下の様にすると所有権を共有することができる

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}

また、Rc::strong_count, Rc::weak_countを利用することで参照数を取得することができる。それぞれの違いは循環参照をカウントするかどうか。

RefCell<T> type

RefCellを利用することで実行時に借用規則を強制することができる様になります。つまりコンパイル時での強制をしなくなるということです。これは内部可変性パターンの実装に用いられます。

内部可変性は、そのデータへの不変参照がある時でさえもデータを可変化できるRustでのデザインパターンです

実用例などは https://doc.rust-jp.rs/book-ja/ch15-05-interior-mutability.html などに記載されていますが、

  • 型の都合上的には不変参照のstructだけど、テスト時のみ、特定の値を書き換えたい

というケースがあったとします。

これを内部可変性パターンが解決します。

短絡的に述べると、

  1. RefCell<T>で実行時の借用規則の強制に切り替え
  2. さらにRefCell<T>で提供されているborrow_mutメソッドを利用するしてRefMut<T>の参照が取得できる
  3. RefMut<T>Derefトレイトを実装しているので、参照外しを用いて値を操作できる

と解釈しました

ref

kqitokqito

SendSync

スレッド間のやりとりのデモを模写しているときにdyn FnBox + Send + 'staticのような部分が理解ができなかったのでメモ

Send マーカートレイト

Sendマーカートレイトを実装した型はスレッド間の所有権の転送を許可するものです。

マーカートレイトとは

マーカートレイトとはメソッドを持たないトレイトのことで、主にトレイト境界での利用が行われる

プリミティブな型にはSendが実装されている

プリミティブな型にはSendが実装されているため、スレッド間での所有権の転送を行うことができます。

しかし、Rc<T>などは実装されていません。Rc<T>はパフォーマンスなどのためにマルチスレッドな環境で動作することを想定していないためです。従ってRc<T>などをthread::spawnなどでmoveしようとするとエラーが発生するはず。

その場合,マルチスレッドな所有権の共有で利用することができるArc<T>を利用することができます。

Mutex

Mutexを利用することでArcを利用したマルチスレッドなやり取りの中で、「どんなときも1つのアクセスしか受け付けない」ことを実装することができます。

use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();

            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

Syncマーカートレイト

Sendマーカートレイトと同時に説明が見受けられるSync ですが、これは「複数のスレッドから参照を受けても安全」ということを示すマーカートレイトです。(T型における&Tが安全だということ)

Sendマーカートレイト同様にRc<T>Syncが実装されてないです。またRefCell<T>が行う実行時の借用規則のチェックはスレッド安全ではないため、Sync`が実装されていません

Ref

kqitokqito

ライフタイム

ライフタイムとは、その参照が有効になるスコープのこと。その目的はダングリング参照を回避すること。

他のスクラップ同様、The Rust Programming Lnauageから参照。

ダングリング参照とは

{
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}

のようなスコープが抜けてしまう値を参照してしまうので上記のコードはコンパイルできません。コンパイルを通すためにはライフタイムを参照するコードの部分と同じかそれ以上にすることが必要です。

ライフタイム注釈記法

以下の様なコードがあるとします。関数longestは引数xとyの参照を比べて大きい方の参照返す関数です。

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    // 最長の文字列は、{}です
    println!("The longest string is {}", result);
}

これはライムタイムの観点から問題がありコンパイルが通りません。longest関数からみて関数xとyの参照のライフタイムや返り値のライフタイムもどいった用途なのかが分からないからです。

このコンパイルを通すためには、それぞれの引数や返り値のライフタイムを明示的に宣言して、同じ期間生きる値だということを指定する必要があります。

下記の例ではライフタイムを'aと名付けて、それを参照に付与している例です

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

ライフタイムを構造体で定義する

以下の様にstruct内部でライフタイムを定義することも可能です。ライフタイムとその構造体での結びつきを明示的にしたいときには利用できそうです

struct ImportantExcerpt<'a> {
    part: &'a str,
}

ライフタイム省略

いくつかの関数の書き方ではライフタイムがコンパイラによって推論されてるので、明示的に定義する必要がこのスクラップ記述時点では必要なくなっています。

またこの分野まりでは以下の用語があるそうです

  • ライフタイム省略規則: コンパイラの参照解析に落とし込まれたライフタイムのパターンのこと
  • 入力ライフタイム: 関数やメソッドの引数のライフタイム
  • 出力ライフタイム: 返り値のライフタイム

静的ライフタイム

'staticライフタイムは特殊なライフタイムでプログラム全体の期間を示します。

// 静的ライフタイムを持ってるよ
const NUM: usize = 100;
let s: &'static str = "I have a static lifetime.";

このs変数はプログラムのバイナリに直接格納され、常に利用可能になります。

トレイト境界などでT: 'staticなどといった値があったら、そのままの意味ですが'staticライフタイムを実装した値でないといけないということですね

Ref

kqitokqito

#dyn keyword
version 1.58.1 にて https://doc.rust-jp.rs/book-ja/ch20-02-multithreaded.html を模写している時に以下のようなtypeの宣言時、コンパイルエラーが出たのでその調査。

trait FnBox {
    fn call_box(self: Box<Self>);
}

impl<F: FnOnce()> FnBox for F {
    fn call_box(self: Box<F>) {
        (*self)()
    }
}

type Job = Box<FnBox + Send + 'static>;


### 以下エラー文
[rustc bare_trait_objects] [W] trait objects without an explicit `dyn` are deprecated

note: `#[warn(bare_trait_objects)]` on by default
warning: this is accepted in the current edition (Rust 2018) but is a hard error in Rust 2021!
note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/warnings-promoted-to-error.html>
help: use `dyn`: `dyn FnBox + Send + 'static`

エラー文に従って以下のようにdynキーワードをFnBoxtraitの前に記述するとエラーが解消される

type Job = Box<dyn FnBox + Send + 'static>;

これはなに

https://doc.rust-lang.org/std/keyword.dyn.html を見てみると

dyn is a prefix of a trait object’s type.
The dyn keyword is used to highlight that calls to methods on the associated Trait are dynamically dispatched. To use the trait this way, it must be ‘object safe’.

とあります。

つまり?

https://doc.rust-jp.rs/edition-guide/rust-2018/new-keywords.html?highlight=dyn#トレイトオブジェクトを表す-dyn-trait

dyn Traitが新しいキーワードとして導入されたエディションガイドを閲覧してみます。

https://doc.rust-jp.rs/edition-guide/rust-2018/new-keywords.html?highlight=dyn#トレイトオブジェクトを表す-dyn-trait

ここにわかりやすい例が記載されている通り、

trait Trait {}

impl Trait for i32 {}

// old
// いままで
fn function1() -> Box<Trait> {
}

// new
// これから
fn function2() -> Box<dyn Trait> {
}

のような感じにオブジェクトトレイトを使い時には明示的にdyn キーワードを付与しないといけないことにversion 1.27からなったようです。

なぜ?

詳しくは追っていませんが、

  • トレイトオブジェクトにトレイト名をそのまま利用するのは良くない
    • トレイトとトレイトオブジェクトの見た目の区別かつかない
      • e.g. impl<T> SomeStruct<T> where T: SomeTraitImpl SomeTrait for SomeStructの違いがつかない
  • コンパイルエラーの曖昧さ...?

代替手段

上記の記事にも記載されている通り、

特定のトレイトを実装した異なる型を共通して扱いたいとき、大抵はトレイトオブジェクトを使う必要はありません。 単一のコンテナに複数の型の構造体を入れたい場合、enum を使えばよいです。

と記載されているのでトレイトオブジェクトの利用の前にenumを利用した方が簡潔に記述できるケースもあるかもしれません

Ref

kqitokqito

早期リターン

筆者はTSなどを利用してwebフロントの開発をする時に早期リターンを心掛けて、なるべくネストしないソースコードを書いて可読性の担保を意識しているのだが、rustではどのようにできるか調べたメモ

結論

https://doc.rust-jp.rs/rust-by-example-ja/error/result/early_returns.html

match フロー制御演算子を利用すればOptionでもResult型でも早期リターンができる

例えばResult型の判断リングの場合だと

let hoge = match result_match { 
  Ok(ok) => ok,
  Err(err) => return "err!!"
} 

のようにreturnを明示的に書けばhoge変数はok変数と同じ方に推論される

あとは普通にif の中でreturnも可能

kqitokqito

Refutabillity(論駁可能性 = パターン)

Refutabillityとは

  • パターン可能かどうか(適切な言葉どうかはわからないが型ベースにおけるパターンマッチング)

if letにおけるRefutabillity

  • letには本来、パターンに該当するならと言う意味がある
  • 論駁可能(パターンにマッチングしないなら)場合にブロック内部を実行できる
    • 下記の例で言うと、PATTERN(左辺)の種類として、None3が考えられる
    • 3が評価された場合にはブロックを実行すると言う意味になる
if let Some(x) = Some(3) { 
    assert_eq!(x, 3);
};

参考

kqitokqito

Unit type 「()

Unit type とは

TypeScript出身からすると見慣れなかったので

https://doc.rust-lang.org/std/primitive.unit.html 曰く、

The () type has exactly one value (), and is used when there is no other meaningful value that could be returned.

とのことで、returnする値の使い道がないことを示すprimitiveらしい。厳密はもちろん用途は異なると思うが、never的な立ち位置な気もする

;の有無

上記の例に記載してある通り、;の有無でreturnする型と値が異なる

fn returns_i64() -> i64 {
    1i64
}
fn returns_unit() {
    1i64;
}

let is_i64 = {
    returns_i64()
};
let is_unit = {
    returns_i64();
};

参考

kqitokqito

文字列

&strString

https://blog.ojisan.io/many-copies-original-sin/#string が参考になった

todo!()

構造体における考慮

悩ましい点

  • &strの場合には参照なのでライフタイムが構造体とその文字列が異なる場合コンパイルできない
  • Stringの場合、&strStringにするにはコピーすることになるのでallocationを考慮したい

結果

genericを使って必要に応じて変換をすれば良い

pub struct Person {
    name: String
}

impl Person {
    pub fn new(name: impl Into<String>) -> Person {
        Person { name: name.into() }
    }
}

参考

kqitokqito

Default trait

https://doc.rust-lang.org/std/default/trait.Default.html

初期値を決めれるトレイト

A trait for giving a type a useful default value.

とあるように、::new()みたいな構造体からその値を返すみたいにderiveすれば簡単にそれを実装できるtrait

#[derive(Default)]
struct SomeOptions {
    foo: i32,
    bar: f32,
}

fn main() {
    let options: SomeOptions = Default::default();
}

便利だ〜

kqitokqito

traitで実装したmethodのスコープについて

前提

自前で宣言したstructに対して適切なスコープでtraitを実装したかった

過去思っていたこと

そのためそのstructに対して自前のtraitを幾つも実装をしたくなかった。

と、考えていたのは以下のようなコードを利用していたため

impl<'a, I: Input<'a>> From<&'a mut I> for Lexer<'a, I> {
    fn from(input: &'a mut I) -> Lexer<'a, I> {
        Lexer { input }
    }
}

上記は別modにおいて以下のようにしてfrom methodを利用することができる

Lexer::from(****)

この考えをもとに借りにtrait BindのようなカスタムtraitをLexer structに実装するとstructのscope == traitのscopeだとしか利用できないと思ってイアt

また、特定のスコープに対してimplキーボードを絞ることも出来なさそうな雰囲気を感じた

impl<'a, I: Input<'a>> Bind<&'a mut I> for Lexer<'a, I> {
  fn bind() {}
}

// どのモジュールでもbindが呼び出せてしまう
Lexer::bind()

気づいた事

と思っていたのだが、Bind traitに紐づくメソッドを呼び出すには該当するmodにおいて use declarationsなどを利用して明示的に宣言をする必要があるらしい

use Binder; // これがないと動作しない

Lexer::bind()

今まではstdで定義されているFromトレイトなどはグローバル定義だったため勘違いをしていた...

のでtrait自体を適切なスコープに対して露出するような宣言をすればbind()が利用されるこもない

結果

結果としてstructにtraitを実装してもスコープは絞れる