Rust 基礎メモ
Rustで調べたことをざっとメモるところ
- 大雑把なメモ
- 厳密・本質的には意味合いが異なる事があるかも
- Rust初心者が記述している
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に対して実装する必要があるらしいということ
今回はunwrapなどを利用する為にTestError
enumに対してDebug トレイトを実装している
ref
Where keyword
Where keywordを利用する事でGenericに対して ~~~ の値を渡してね
という制約を設ける事ができる。
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
}
これはTypeScriptなどでいうところのGeneric Constraintsと似ている概念かも :think:
ちなみに
によると引数に対して直接 Constraints も設ける事ができるが、Where keywordを用いる事で表現力が増すケースがあるらしい
クロージャ
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: と調べたらここら辺で定義されているっぽい
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
- https://kanejaku.org/posts/2018/12/rust-closure-types/ (こちらの記事が非常に参考になりました
スマートポインタ
スマートポインタ 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だけど、テスト時のみ、特定の値を書き換えたい
というケースがあったとします。
これを内部可変性パターンが解決します。
短絡的に述べると、
-
RefCell<T>
で実行時の借用規則の強制に切り替え - さらに
RefCell<T>
で提供されているborrow_mut
メソッドを利用するしてRefMut<T>
の参照が取得できる -
RefMut<T>
はDeref
トレイトを実装しているので、参照外しを用いて値を操作できる
と解釈しました
ref
Send
と Sync
スレッド間のやりとりのデモを模写しているときに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
ライフタイム
ライフタイムとは、その参照が有効になるスコープのこと。その目的はダングリング参照を回避すること。
他のスクラップ同様、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
#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
キーワードをFnBox
traitの前に記述するとエラーが解消される
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’.
とあります。
つまり?
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: SomeTrait
とImpl SomeTrait for SomeStruct
の違いがつかない
- e.g.
- トレイトとトレイトオブジェクトの見た目の区別かつかない
- コンパイルエラーの曖昧さ...?
代替手段
上記の記事にも記載されている通り、
特定のトレイトを実装した異なる型を共通して扱いたいとき、大抵はトレイトオブジェクトを使う必要はありません。 単一のコンテナに複数の型の構造体を入れたい場合、enum を使えばよいです。
と記載されているのでトレイトオブジェクトの利用の前にenumを利用した方が簡潔に記述できるケースもあるかもしれません
Ref
早期リターン
筆者はTSなどを利用してwebフロントの開発をする時に早期リターンを心掛けて、なるべくネストしないソースコードを書いて可読性の担保を意識しているのだが、rustではどのようにできるか調べたメモ
結論
match フロー制御演算子を利用すればOptionでもResult型でも早期リターンができる
例えばResult型の判断リングの場合だと
let hoge = match result_match {
Ok(ok) => ok,
Err(err) => return "err!!"
}
のようにreturnを明示的に書けばhoge
変数はok
変数と同じ方に推論される
あとは普通にif の中でreturn
も可能
Refutabillity(論駁可能性 = パターン)
Refutabillityとは
- パターン可能かどうか(適切な言葉どうかはわからないが型ベースにおけるパターンマッチング)
if let
におけるRefutabillity
- letには本来、パターンに該当するならと言う意味がある
- 論駁可能(パターンにマッチングしないなら)場合にブロック内部を実行できる
- 下記の例で言うと、PATTERN(左辺)の種類として、
None
か3
が考えられる -
3
が評価された場合にはブロックを実行すると言う意味になる
- 下記の例で言うと、PATTERN(左辺)の種類として、
if let Some(x) = Some(3) {
assert_eq!(x, 3);
};
参考
()
」
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();
};
参考
モジュール参照
extern crate
mod
use
違い
-
extern crate
: クレートの参照 -
mod
: 子モジュールを参照 -
use
:pub
要素の参照
参考
文字列
&str
かString
か
https://blog.ojisan.io/many-copies-original-sin/#string が参考になった
todo!()
構造体における考慮
悩ましい点
-
&str
の場合には参照なのでライフタイムが構造体とその文字列が異なる場合コンパイルできない -
String
の場合、&str
をString
にするにはコピーすることになるのでallocationを考慮したい
結果
genericを使って必要に応じて変換をすれば良い
pub struct Person {
name: String
}
impl Person {
pub fn new(name: impl Into<String>) -> Person {
Person { name: name.into() }
}
}
参考
Inline化
概要
#[inline]
fn cur(&mut self) -> Option<char> {
self.iter.clone().next().map(|i| i.1)
}
のように #[inline]
を付与することでそのコードが文字通りインライン展開される。呼び出し頻度が高かったりするとパフォーマンスが向上するケースがあるらしい
参考
Default trait
初期値を決めれるトレイト
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();
}
便利だ〜
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を実装してもスコープは絞れる