Reference

日本語

Home | Rustの日本語ドキュメント/Japanese Docs for Rust

English

Learn Rust - Rust Programming Language

standard

Exception Handling

📚 Reference

  • https://doc.rust-lang.org/std/result/index.html
  • https://doc.rust-lang.org/std/option/index.html

📝 Report

はじめに

RustのResult, Optionについて整理しておきたい。 どちらも列挙型です。これらの型を用いることで、例外処理を扱います。 Rustのバージョンは1.75.0です。

Result型

#![allow(unused)]
fn main() {
enum Result<T, E> {
   Ok(T),
   Err(E),
}
}

Methods

(ほぼOptionと同じだ)

Option型

#![allow(unused)]
fn main() {
pub enum Option<T> {
    None,
    Some(T),
}
}

Option<T>型はNoneまたはSome(T)を指します。 Noneは他言語でいうnullです、値がないことを示します。 Some(T)はT型の値を持つSome型を示します。

Methods

使い分けが難しいと感じるものを整理します。

Some(T)を取り出す

Noneが返る場合の挙動が下記のように異なります。

型を変換する

Result型へ
  • ok_or
    • Some(T)Ok(T)へ、NoneErr(err)へ。errは指定した値。
  • ok_or_else
    • Some(T)Ok(T)へ、NoneErr(err)へ。errは指定したstd::ops::FnOnce
  • transpose
    • Option<Result<T>>Result<Option<T>>
Someを変える
  • filter
    • trueを返す値をSome(T)とする
  • flatten
    • Option<Option<T>>Option<T>
  • map
    • Option<T>Option<U>
型を変える

Some(T)の場合、指定したstd::ops::FnOnceの戻り値を返す

その他

下記は省略しました、公式ドキュメントを参照しましょう。

  • Querying the variant
  • Adapters for working with references 参照型との調整
  • Boolean operators
  • Comparison operators
  • Iterating over Option
  • Collecting into Option
  • Modifying an Option in-place

共通

? operator

?オペレーターを使用した値は Noneが返る場合は近しいブロックをNoneで抜けます。 それ以外はSome(T)unwrap()した値になります。

expression

Rustはいわゆる式言語の一つ。すべてを式で扱う。1

CRust
if/switchの扱い
if/switchを式の中で使えるかNoYes
#![allow(unused)]
fn main() {
let status =
  if cpu.temperature <= MAX_TEMP {
    HttpStatus::Ok
  } else {
    HttpStatus::ServerError
  };
}

Rustはif式を変数へ直接bindできるので、三項演算子をもたない。

test

テストの記述法 - The Rust Programming Language 日本語版

Rustには簡単なテストフレームワークが組み込まれている。 テストはマルチスレッドで並列実行される。これはcarto test -- --test-threads 1のように調整できる。 テストが失敗した結果のみ出力されるので、成功結果も出力したければcargo test -- --no-captureとする。

Unit test

ユニットテスト - Rust By Example 日本語版

assersionする

テストは#[test]属性が付与された通常の関数である。 下記を利用する。

上記は非テストでも利用できる。つまりリリースビルドにも入ってしまう。 デバッグビルド時のみ有効にしたければ下記を利用する。

もっといえば、テスト時のみ実行するように#[cfg]属性で設定するのが慣習となっている。

panicする

エラーが起こる場合を正常としたテストをしたいなら、#[should_panic]属性を利用する。 パニックが起こることが自明な静的コードの場合、#[allow]属性でコンパイラへ指示する。

Integration test

インテグレーションテスト - Rust By Example 日本語版

Cargoはsrc/と同じ階層のtests/配下を統合テストとして扱う。 クレートを外部のユーザの世界から見る。つまりクレートの公開APIをテストすることがポイント。

cargo test: 単体テスト、統合テストを実行する cargo test --test foo: tests/foo.rsに書かれた統合テストを実行する

struct

impl

メソッドは構造体定義の中ではなく、implブロックに記述する。

  • 関連関数
    • implブロック内に定義した関数
    • 呼び出される対象の値Selfを最初の引数としなければならない
  • 自由関数
    • implブロック外に定義した関数
  • 型関連関数
    • 特定の型に対するimplブロック内に定義した関数
    • selfを引数として持たない
      • コンストラクタ
        • ちなみにコンストラクタ関数をnewとするのは慣習
    • メソッドと区別される
    • Foo::newという形で使用する

定数

  • 型関連定数
    • インスタンスを参照せずに使用できる
    • Foo::Tという形で使用する

trait

  • 拡張トレイト
    • 既存の方にメソッドを追加するためだけのトレイト

型関連関数

トレイトに型関連関数を持たせることができる。 トレイトオブジェクトでは型関連関数をサポートしていない。 where Self: Sizedを指定することでトレイトオブジェクトでの使用を除外する、これでトレイトオブジェクトを作ることができるようになる。つまり、型関連関数は使用できないが関連関数は使用できる状態になる。

関連型

トレイトでメソッドを実行する以上のことをしたいなら、関連型は有用。

#![allow(unused)]
fn main() {
pub trait Iterator {
  type Item;
  fn next(&mut self) -> Option<Self::Item>;
}
}

Mul<i32>Mul<f64>は異なるトレイトとして認識される。 ジェネリックトレイトは孤児ルールに関して特別な待遇を受けている。 例えばWindowSizeを自クレートで定義したなら、impl Mul<WindowSize> for i32のように外部トレイトを外部の型へ実装できる。トレイトが独自であり衝突することがないからだ。 impl<T> Mul<WindowSize> for Vec<T>のようにジェネリックにすることもできる。

#![allow(unused)]
fn main() {
pub tarit Mul<RHS> {
  type Output;
  fn mul(self, rhs: RHS) -> Self::Output;
}
}

impl Trait

#![allow(unused)]
fn main() {
fn cyclical_zip(v: Vec<u8>, u: Vec<u8>) -> impl Iterator<Item=u8> {
  v.into_iter().chain(u.into_iter()).cycle()
}
}

利点

  • より関数の意図を表す場合がある
  • インターフェースだけを指定できるので変更に強い

注意点

  • impl Traitは静的ディスパッチの一つなので、コンパイル時にその関数が返す型が決定していなければならない
  • トレイトメソッドではimpl Traitを返り値の型として指定できない。自由関数や特定の型に関連した関数に対してのみ使用できる
  • print::<i32>(42)のように型を明示して呼び出すことはできない。ジェネリクス関数ではできる
  • impl Traitでは特定の無名型パラメータが割り当てられる? したがって実現できるのは単純なジェネリクスのみ。引数の型に関係性を持つような関数は表現できないらしい。

下記は同等だが、impl Taritでは呼び出し方に制限がある。

#![allow(unused)]
fn main() {
// ジェネリクス関数
fn print<T: Display>(val: T) {
  println!("{}", val);
}
print::<i32>(42);

// impl Trait
fn print(val: impl Display) {
  println!("{}", val);
}
print(42);
}

関連定数

構造体や列挙型と同じように、トレイトにも関連定数を持たせることができる。 トレイトの場合は値を指定する必要はない(指定してもよい)。値は実装の際に指定できる。

#![allow(unused)]
fn main() {
trait Greet {
  const GREETING: &'static str = "Hello";
  const TYPE: Self;
}
}

注意点

  • トレイトオブジェクトでは使用できない。コンパイル時に実装に関する型情報を用いて正しい値を選択するから

トレイトオブジェクト

traitを利用して多層性を表現する方法の一つ

コンパイル時にサイズが決まっていなければならないものーー変数などではdyn Write型を指定できない。 したがって例えば参照ならばサイズが決まるので指定できる。 このような、トレイト型への参照をトレイトオブジェクトと呼ぶ。

#![allow(unused)]
fn main() {
let mut buf: Vec<u8> = vec![];
// error
let writer: dyn Write = buf;
// ok
let writer: &mut dyn Write = &mut bef;
}

メモリ上ではファットポインタ

  • 値へのポインタ
  • 値の型を表すテーブル(仮想テーブル)へのポインタ

Rustの仮想テーブルはコンパイル時に一度だけ作られ、同じ型のすべてのオブジェクトによって共有される。

Rustは通常の参照を必要に応じて自動的にトレイトオブジェクトへ変換する say_helloの引数の型は&mut dyn WriteだがFileはWriteトレイトを実装しているのでlocal_fileはそのまま渡してもトレイトオブジェクトヘ自動変換される。

#![allow(unused)]
fn main() {
let mut local_file: File = File::create("hello.txt");
say_hello(&mut local_file)?;
}

marker trait

From, Into

Intoは、一般に関数が受け取る引数を柔軟にするために用いられる。

Fromは、ある型から別の型のインスタンスを生成するために用いられる。

適切なFromの実装があれば、標準ライブラリが自動的に対応するIntoトレイトを実装してくれる。

IntoやFromは変換が安価であることを保証しない。(対称的にAsRefやAsMutなどの変換は安価であることが期待されている) つまりヒープを確保したり、コピーしたりしてもよい。

?演算子はFromとIntoを用いて、特定のエラー型を一般的なエラー型に必要に応じて変換することで複数の失敗の仕方がありうる関数のコードを綺麗に書けるようにしてくれる。

archtecture

Translations - Rust Design Patterns

Rust の DI を考える — Part 1: DI とは何だったか - paild tech blog

メモ

  • 上記著者から取得したDIの種類
    • airframe di
    • cake pattern di
    • constructor di
    • function di
    • reader di
    • shaku di

Hunble Object

Humble Object at XUnitPatterns.com

テストが困難な複雑なオブジェクトをシンプルにテスト可能な形にすること

cake pattern

導入

まずは概念からまとめておきたい。

Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 #Scala - Qiita

下記はCake PatternによるDIを実装しないパターン。 UserServiceUserRepositoryのみでなくUserRepositoryImplへ依存しているため、UserRepositoryImplの変更がUserServiceへ影響してしまう。

trait UserRepository {
  // 略
}

object UserRepositoryImpl extends UserRepository {
  // 略
}

object UserService {
  val userRepository: UserRepository = UserRepositoryImpl // ← ここでImplを参照しているのが問題
  // 略
}

下記はCake PatterによるDIを実装したパターン。 独自解釈すると、、 UserService: サービス UserRepository: 外部実装の抽象 UserRepositoryImpl: 外部実装 があったのに対し、下記を追加実装し、結合箇所は抽象層のみとした。 UserService(trait): サービスの抽象 UserRepositoryComponent: 「外部実装の抽象」の抽象 UserRepositoryComponentImpl: 「外部実装の抽象化」の抽象

trait UserRepositoryComponent {
  val userRepository: UserRepository

  trait UserRepository {
    // 略
  }
}

trait UserRepositoryComponentImpl extends UserRepositoryComponent {
  val userRepository = UserRepositoryImpl

  object UserRepositoryImpl extends UserRepository {
    // 略
  }
}

trait UserService {
  this: UserRepositoryComponent =>
  // 略
}

object UserService extends UserService with UserRepositoryComponentImpl

Minimal Cake Pattern 再考 #テスト - Qiita

Rustでの実践

RustのDI | κeenのHappy Hacκing Blog Cake PatternでDIしてみた | blog.ojisan.io Rust で DI | blog.ojisan.io

minimal Cake Pattern

導入

Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 #Scala - Qiita

// インターフェース
trait UsesUserRepository {
  val userRepository: UserRepository
}
trait UserRepository {
  // 略
}

// 実装側
trait MixInUserRepository extends UsesUserRepository {
  val userRepository = UserRepositoryImpl
}
object UserRepositoryImpl extends UserRepository {
  // 略
}

// 利用側
trait UserService extends UsesUserRepository {
  // 略
}
object UserService extends UserService with MixInUserRepository

Minimal Cake Pattern のお作法 #Scala - Qiita

Rustでの実践

Rust の DI を考える –– Part 2: Rust における DI の手法の整理 - paild tech blog

async

Getting Started - Asynchronous Programming in Rust

async/await

Pinning

core::pincore::marker::Unpinと連動する。

Pinningは!Unpinを実装した式がmoveしていないことを示し、asyncブロック内で値への参照を保証する。

Why

PinningはRustで安全性が十分に担保できていないケースに必要。1

例)自己参照型 例)侵入的データ構造

自己参照型の構造体がmoveすると、自己参照先が不正となる例

Rust Playground

#[derive(Debug)]
struct Test {
    a: String,
    b: *const String,
}

impl Test {
    fn new(txt: &str) -> Self {
        Test {
            a: String::from(txt),
            b: std::ptr::null(),
        }
    }

    fn init(&mut self) {
        let self_ref: *const String = &self.a;
        self.b = self_ref;
    }

    fn a(&self) -> &str {
        &self.a
    }

    fn b(&self) -> &String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

fn main() {
    let mut test1 = Test::new("test1");
    test1.init();
    let mut test2 = Test::new("test2");
    test2.init();

    println!("a: {}, b: {}", test1.a(), test1.b());
    assert_eq!("test1", test1.a());
    assert_eq!("test1", test1.b());
    std::mem::swap(&mut test1, &mut test2);
    println!("a: {}, b: {}", test2.a(), test2.b());
    assert_eq!("test1", test2.a());
    assert_eq!("test1", test2.b());
}

Fig 1: Before and after swap before and after swap

侵入的データ構造の例

WIP: core::pin - Rust


1

core::pin - Rust
This concept of “pinning” is necessary to implement safe interfaces on top of things like self-referential types and intrusive data structures which cannot currently be modeled in fully safe Rust using only borrow-checked references.

Executing Multiple Futures at a Time

複数の非同期関数を並行処理する方法の例

join!

join!が返す値は、各Future結果のタプル。1 FutureがResultを返す場合、try_join!がよいらしい。

example

検証時のsleepとして、std::thread::sleepは同期的にブロックしてしまうため、非同期用のsleepが必要。2

tokio::time::sleepを使用した例

Rust Playground

use std::time::Duration;

async fn dance1() { 
    println!("dance1.");
    tokio::time::sleep(Duration::from_secs(10)).await;
    println!("dance1.");
}
async fn dance2() { 
    println!("dance2.");
    tokio::time::sleep(Duration::from_secs(1)).await;
}
async fn dance3() { 
    println!("dance3.");
    tokio::time::sleep(Duration::from_secs(1)).await;
}

#[tokio::main]
async fn main() {
    tokio::join!(dance1(), dance2(), dance3());
}

色々なjoin

色々なjoinがある。差分は不明。対応するランタイムが違うだけならよいが。

source code

select!

基本

複数の非同期処理のうち一つが完了すれば、応じた処理をする。 他の非同期処理の完了は待たない。例えば副作用関数や不純関数の場合はどういう扱いになるのだろうか? select!も式なのでそれぞれの返り値は同じ型でなければならない。

syntax: <pattern> = <expression> => <code>,

select!に渡すFutureはUnpinとfutures::future::FusedFutureを実装する必要がある3 Unpinはselectが可変参照を取得するために必要。moveしては後続処理ができないらしい。 FusedFutureはselectが完了した後にpollしないように必要。FusedFutureは互いに完了したかどうかを追跡する。selectループで完了していないFutureのみpollするために必要。 future::readyによって変えるFutureはFusedFutureを実装しているので、再度pollされないような仕組み。

Streamはfutures::stream::FusedStreamに対応している。

Concurrent tasks in a select loop

futures::future::Fuse::terminatedは既に完了したFutureをFuseできるので、selectループで便利らしい。

source code

Spawning

spawnはJoinHandleを返す。

JoinHandleはFutureを実装しているのでawaitするまで結果を得られない。4

mainタスクとspawnされたタスクとのやり取りとして、channelsを利用する。


1

join! - Asynchronous Programming in Rust
The value returned by join! is a tuple containing the output of each Future passed in.

2

rust - Concurrent async/await with sleep - Stack Overflow
Since the standard/original thread::sleep is blocking, it turns out that the async library is providing async_std::task::sleep( ... ) which is the nonblocking version for sleep.

3

select in futures - Rust
Futures directly passed to select! must be Unpin and implement FusedFuture.

4

Spawning - Asynchronous Programming in Rust
The JoinHandle returned by spawn implements the Future trait, so we can .await it to get the result of the task.

Send

非同期関数のFutureがSendかどうかは、Sendでない方が.awaitをまたがって生存しているかどうかで決まる。1


1

Send Approximation - Asynchronous Programming in Rust
Whether or not an async fn Future is Send is determined by whether a non-Send type is held across an .await point.

macro

example

O'Reilly Japan - プログラミングRust 第2版からチュートリアル。

マクロ定義/展開はコンパイル初期のコード読み込み時点で行われる。 マクロ呼び出しは定義後でなければならない。

マクロパターンはコードに対する正規表現のようなもので、トークンという単位に対して操作を行う。コメントやスペースはトークンではないのでいくら入れてもマッチに影響しない。 出力テンプレートでは繰り返しパターンを利用できる。

TODO: <[_]>などの表現について


sqlx::query_as

WIP: テスト的に下記へ記しておく。到底すべてまとめられるわけではないので、今後のまとめ方次第では削除する。

#![allow(unused)]
fn main() {
#[macro_export]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
macro_rules! query_as (
    ($out_struct:path, $query:expr) => ( {
        $crate::sqlx_macros::expand_query!(record = $out_struct, source = $query)
    });
    ($out_struct:path, $query:expr, $($args:tt)*) => ( {
        $crate::sqlx_macros::expand_query!(record = $out_struct, source = $query, args = [$($args)*])
    })
);
}

sqlx_macros::expand_query

#![allow(unused)]
fn main() {
#[proc_macro]
pub fn expand_query(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as query::QueryMacroInput);

    match query::expand_input(input, FOSS_DRIVERS) {
        Ok(ts) => ts.into(),
        Err(e) => {
            if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
                parse_err.to_compile_error().into()
            } else {
                let msg = e.to_string();
                quote!(::std::compile_error!(#msg)).into()
            }
        }
    }
}
}

proc_macro [Rust] Procedural Macroの仕組みと実装方法 Rust の procedural macro を操って黒魔術師になろう〜proc-macro-workshop の紹介

syn::parse_macro_input

sqlx_macros_core::query::expand_input

#![allow(unused)]
fn main() {
pub fn expand_input<'a>(
    input: QueryMacroInput,
    drivers: impl IntoIterator<Item = &'a QueryDriver>,
) -> crate::Result<TokenStream> {
    let data_source = match &*METADATA {
        Metadata {
            offline: false,
            database_url: Some(db_url),
            ..
        } => QueryDataSource::live(db_url)?,

        Metadata { offline, .. } => {
            // Try load the cached query metadata file.
            let filename = format!("query-{}.json", hash_string(&input.sql));

            // Check SQLX_OFFLINE_DIR, then local .sqlx, then workspace .sqlx.
            let dirs = [
                || env("SQLX_OFFLINE_DIR").ok().map(PathBuf::from),
                || Some(METADATA.manifest_dir.join(".sqlx")),
                || Some(METADATA.workspace_root().join(".sqlx")),
            ];
            let Some(data_file_path) = dirs
                .iter()
                .filter_map(|path| path())
                .map(|path| path.join(&filename))
                .find(|path| path.exists())
            else {
                return Err(
                    if *offline {
                        "`SQLX_OFFLINE=true` but there is no cached data for this query, run `cargo sqlx prepare` to update the query cache or unset `SQLX_OFFLINE`"
                    } else {
                        "set `DATABASE_URL` to use query macros online, or run `cargo sqlx prepare` to update the query cache"
                    }.into()
                );
            };

            QueryDataSource::Cached(DynQueryData::from_data_file(&data_file_path, &input.sql)?)
        }
    };

    for driver in drivers {
        if data_source.matches_driver(&driver) {
            return (driver.expand)(input, data_source);
        }
    }

    match data_source {
        QueryDataSource::Live {
            database_url_parsed,
            ..
        } => Err(format!(
            "no database driver found matching URL scheme {:?}; the corresponding Cargo feature may need to be enabled", 
            database_url_parsed.scheme()
        ).into()),
        QueryDataSource::Cached(data) => {
            Err(format!(
                "found cached data for database {:?} but no matching driver; the corresponding Cargo feature may need to be enabled",
                data.db_name
            ).into())
        }
    }
}
}

procedure

Attribute macros

itemsouter attributes を定義するマクロ。つまりTrait実装などを継承させるマクロ。

proc_macro_attributeAttributeを使ってpublicなfunctionへ定義する。

example

sqlxより、

#![allow(unused)]
fn main() {
#[cfg(feature = "macros")]
#[proc_macro_attribute]
pub fn test(args: TokenStream, input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as syn::ItemFn);

    match test_attr::expand(args.into(), input) {
        Ok(ts) => ts.into(),
        Err(e) => {
            if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
                parse_err.to_compile_error().into()
            } else {
                let msg = e.to_string();
                quote!(::std::compile_error!(#msg)).into()
            }
        }
    }
}
}

inputはsyn::parseでパースする。syn::parse::Parsetraitを実装済みであれば上記のようにasでパース先を指定できる。(asはキャストなどではなくマクロ定義の一部であることに注意)

debug

# windows
set RUST_BACKTRACE=1
# linux
export RUST_BACKTRACE=1

cargo test

How do I run 'cargo test' with RUST_BACKTRACE=1 on Windows? : r/rust