Reference
日本語
Home | Rustの日本語ドキュメント/Japanese Docs for Rust
- The Rust Programming Language 日本語版 - The Rust Programming Language 日本語版
- Introduction - Rust By Example 日本語版
English
Learn Rust - Rust Programming Language
- The Rust Programming Language - The Rust Programming Language
- Introduction - Rust By Example
- Introduction - The Rust Edition Guide
- Introduction - The Cargo Book
- What is rustdoc? - The rustdoc book
- What is rustc? - The rustc book
- Rust error codes index - Error codes index
- Getting started - Command Line Applications in Rust
- Introduction - Rust and WebAssembly
- Introduction - The Embedded Rust Book
- Introduction - The Rust Reference
- Introduction - The Rustonomicon
- The Unstable Book - The Rust Unstable Book
- std - Rust
- Introduction - Clippy Documentation
- About - Rust API Guidelines
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が返る場合の挙動が下記のように異なります。
- exprect
- 指定した文言でpanicさせる
- unwrap
- コンパイラ?生成文言でpanicさせる
- unwrap_or
- 指定した値を返す
- unwrap_or_default
- unwrap_or_else
- 指定したstd::ops::FnOnceの戻り値を返す
型を変換する
Result型へ
- ok_or
Some(T)をOk(T)へ、NoneをErr(err)へ。errは指定した値。
- ok_or_else
Some(T)をOk(T)へ、NoneをErr(err)へ。errは指定したstd::ops::FnOnce。
- transpose
Option<Result<T>>をResult<Option<T>>へ
Someを変える
型を変える
Some(T)の場合、指定したstd::ops::FnOnceの戻り値を返す
- map_or
Noneの場合、指定した値を返す
- map_or_else
Noneの場合、指定した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
| C | Rust | |
|---|---|---|
| if/switchの扱い | 文 | 式 |
| if/switchを式の中で使えるか | No | Yes |
#![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を実装しないパターン。
UserServiceがUserRepositoryのみでなく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::pinはcore::marker::Unpinと連動する。
Pinningは!Unpinを実装した式がmoveしていないことを示し、asyncブロック内で値への参照を保証する。
Why
PinningはRustで安全性が十分に担保できていないケースに必要。1
例)自己参照型 例)侵入的データ構造
自己参照型の構造体がmoveすると、自己参照先が不正となる例
#[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

侵入的データ構造の例
WIP: core::pin - Rust
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を使用した例
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()); }
std::thread::sleepを使用した失敗例futures::jointokio::join
色々な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されないような仕組み。
futures::select- Rust Playground
- TODO: なぜ結果が一つに収束するのだろうか
tokio::select
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を利用する。
join! - Asynchronous Programming in Rust
The value returned by join! is a tuple containing the output of each Future passed in.
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.
select in futures - Rust
Futures directly passed to select! must be Unpin and implement FusedFuture.
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
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)*]) }) ); }
#![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 の紹介
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
itemsへouter 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