Rust
Links
実装上の推奨やプラクティス
問題形式のチュートリアル
何も分かってないこと
*const
よく見るモジュールの docs
collections
たまに?
crates
所有権
引数の所有権を要する関数は、借用して複製するのではなく所有権を受け取るべきです。
呼び出し側が clone する
temporary value dropped while borrowed
malloc と free を必ず1対1で行うための仕組み
所有権を持つのは1つだけ & スコープから抜けたら所有権のある変数を drop するルールで実現
& で参照、参照を渡す=借用
&mut で変更可能なアクセス権借用、不変な参照を含めてスコープで1つのみ
可変な間は move できない
&mut より後に不変な参照がない
スコープ内でより下で参照を使っている時に、所有権を奪う関数に渡せない
move(代入)しつつ mutable にできる
let mut b = a;
所有権イディオム
左辺にrefをつけることによる借用と、右辺に&をつけることによる借用は等価
構造体をデストラクタする時に
let Point { x: ref ref_to_x, y: _ } = point;
point.x の参照を ref_to_x に
let if Some(ref i) = opt { ... } など Option で使うか?
自前のデータ構造に値を保持するときは所有権を奪っていい
使う側が clone するか判断する
Map
match map.get(k) { ... map.insert(...) } できない
値取りつつなければ初期化
map.entry(key).or_insert(...)
map.entry(key).or_insert_with(|| ...)
match map.entry(k) { Vacant(entry) => entry., Occupied(entry) => ... } あるときないとき
数値
変換は
as や別の精度の数値型の変数に代入
out
u8::try_from(num) すると Result で返る
checked_ メソッドでオーバーフローをチェックしつつ計算
x.checked_add(y)
文字列
つまり文字列リテラルは &'static str
str は UTF-8 bytes、インデックスアクセスはバイト位置
raw string literals ≒ heredoc
r#" ~ "#
string.parse::<i32>() -> Result
Option
opt.and_then(|v| v.val) で Some(v) の v から値を取り出したり参照したり
返すのも Option、Some の中身を見て None 返せるので、map より使うかも
if let& while let での取り出し
code:if_let.rs
let number = Some(7);
if let Some(i) = number {
println!("Matched {:?}!", i);
}
左辺で Some(ref a) で a を参照を得つつデストラクト
.ok_or(...) で Result へ
.flat_map で Some のみ取り出す
Option<String> -> Option<&str> にするには
Result にもある
as_ref, as_mut
それぞれ &Option[T] -> Option[&T], &mut Option[T] ->> Option[&mut T] にする
1要素の iter として扱える & IntoIterator 持っているので以下ができる
vec![1,2,3].extend(Some(4))
vec![1,2,3].iter().chain(Some(4).iter())
attributes
! bang があるのはその項目全体
ネストのないところだったら module 全体
関数内だったらそのスコープ
#![allow(dead_code)] // この行でコンパイラのwaringsメッセージを止めます。
allow, warn, deny, forbid
#[must_use]
fn の返り値受けないと lint にひっかかる
derive
Debug: {:?} でフォーマットできる
Eq, PartialEq: 比較可能になる
Ord, PartialOrd: 順序付け
Clone
Default: デフォルト値、全フィールドに Default が実装されている必要がある
println!
{0}, {1} で引数の順番
use
use std::io::{self, Write} は std::io と std::io::Write を import
オーバーフローをチェックする
checked_ メソッド
x.checked_add(y)
文字列表現
std::fmt::Debug は {:?}
std::fmt::Display は {}
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
実装すると ToString trait が実装される
write! を使う
Array
固定長のもの、[T; length] で宣言、初期化も [value; length]
let ary: [i32; 10] = [0; 10]
slice を引数に取る関数に渡す時は &ary[..] ← full length slice
コード中で固定長なら Vec にせずとも for i in &[i1, i2] {...}
変更したいなら .iter_mut()?
Vec
let v: Vec<i32> = Vec::new();
let v = vec![1,2,3];
slice を得る &v[1..2], &v[..]
デストラクトするときは slice でやる?
code:match_vec.rs
println!("{:?}, {:?}, {:?} with if-let", one, two, three);
}
Map
所有権は map に移動する
Result
Result を返す関数内では ? でearly return できる
str.parse::<i32>()?
if let Ok(v) = res { ... }
.map / .map_err
.and_then で Ok のときだけ実行
res.and_then(|n| Ok(n*2))
.ok() / .err() で Option に
.and() / .or() で Result 同士の積 / 和
型変換系
.into()
From trait を実装すると <B>.into() -> A も実装される
code:impl_from.rs
impl From<FromType> for ToType {
fn from(in: FromType) -> ToType {...}
}
.try_into(), .try_from()
into, from の Result が返る版
to_owned()
参照に対して、clone して所有権を得る &str -> String
.as_deref()
Option<String> -> Option<&str>
Option<Vec<T> -> Option<&[T]>
Option<Rc<T>> -> Option<&T>
などなど、コンテナになる型なら大抵実装されている?
反対が as_ref()
ループ・コレクション操作
.iter() は借用、参照がループされる
.into_iter() は 所有権が移動する
.filter_map Option を返す関数渡して Some だけ集める / Result なら .ok で 成功だけ集められる
ユニークにするなら HashSet に .collect する
イテレータ
.collect() で要求する型で結果が変わる FromIterator
全体を Result で包むのか Vec<Result> なのか
each with index は .iter().enumerate()、(i ,val) の イテレータが返る
エラーハンドリング
独自のエラー型を定義する
struct MyError{}; impl error::Error for MyError {}
Box<dyn Error>
Result<(), Box<dyn Error>>
Box
Vec<Box<dyn MyTrait>>
Box<T>は Deref を実装していて T のメソッドを Box に対して呼び出しても解決される
Box with std::error::Error
Box<dyn Error> で任意のエラー
Result<(), Box<dyn Error>>
Rc / Arc
Rc = Reference Count
Arc = Atomic Reference Count
let arc = Arc::new(...); arc.clone() より Arc::clone(arc) が推奨されている
Cow
Cow = Clone on Write
Cow::from() で作って
変更が必要になったら to_mut()
into_owned()
Cow::Borrowed → Cow::Owned
trait
引数で受ける時は impl
fn hoge(value: impl MyTrait) {...}
複数の Trait を実装していることを期待する
fn hoge(value: impl MyTrait + OtherTrait) {...}
<T: Display> Display を実装している型パラメータ T → 型境界
impl<T: Display> Hoge<T> {...}
既存の型にメソッドを追加
code:append_bar.rs
trait AppendBar {
fn append_bar(self) -> Self;
}
impl AppendBar for String {
fn append_bar(self) -> Self {
self + "Bar"
}
}
構造体
デフォルト値は Default trait を実装するとちょっと便利
Self に関連する型持たせる
code:companion_error.rs
impl MyStruct {
type Error = MyStructError;
fn hoge() -> Result<Hoge, Self::Error> {... }
}
fn のシグネチャ
所有権いらないなら参照で受ける
文字列
大抵 &str -> String になる
構造体の中身参照するようなメソッドでは &str返せる
引数を &String で受ける意味ない、&str
どちらでも受けれるようにするには Into で
fn foo(name: impl Into<String>) -> User
リスト
参照でいいなら Vec<T> より &[T]
関数の引数の型としてありえるのは基本的に&mut Vec<T>, &[T], &mut [T]
呼び出し元にコピーを作るかどうか選ばせる
関数内部でコピー作るより &mut で受けたほうが大抵良い?
slice は返せない、固定長の配列 or Vec
Vec<AddressEntry> でも &[AddressEntry] でもなく impl IntoIterator<Item=AddressEntry> 受ける話
ライフタイム
ライフタイム付きのシグネチャを満たすスコープのときに呼べる
コンパイラがシグネチャを見て借用のスコープ=生存期間を判断できるように付与する
複数の引数から1つを返すとき
複数の引数からそれぞれに対応する値を返すとき
スレッド & チャネル
let handle = thread::spawn(move || {...})
move で所有権渡す
スレッドの中で参照する変数の生存期間が分からない → 所有権を move してスレッド側のスコープが終わったら drop するように宣言する?
Copy trait を実装する値なら move 時にコピーされる
handle.join() で待つ、返り値が Result でやってくる
状態を共有するには Arc & Mutex を使う
let shared = Arc::new(Mutex::new(MyStruct{...}))
spawn 前に Arc::clone() した変数を参照する
変数を操作する前に lock() する
みんな lock().unwrap() してるけど現実では loop しつつ待つ?
lock は blocking, try_lock は block しない
Err になるのは別の mutex を取ったやつが panic した時
なら lock で Err が返したら割とどうしようもない状態、まるごとやり直したりが必要
mpsc = multiple processor, single consumer
let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel();
mpsc::Sender::clone(&tx) で複製して渡していく
tx.send(...)
受信側がドロップしてたら Err
send は所有権を受信側にムーブする
rx.recv() は blocking, .try_recv()は non-blocking
for received in rx {...}
sender がすべて drop すると iterator は終わる / 明に drop 呼んでもいい
マクロ
todo!()
unimplemented!()
macro_rules! で定義
#[macro_export]
test
実行 $ cargo test
$ cargo test -- --show-output print も出力する
$ cargo test hoge hoge を含むテストを実行する
tests::fn_name とかでより具体的に
#[cfg(test)] ブロック
use super::*; で外側を参照できるようにする
cfg attribute によってテスト時だけコンパイルに含めるのでテストでだけ使えるヘルパ定義にも使える?
#[test] アトリビュートを fn につける
assertion
assert!(bool)
assert_eq!(a, b) は == で比較
assert_ne!(a, b) は !=
比較は PartialEq トレイト、出力のために Debug も実装しておくとよい
[derive(PartialEq, Debug)]
いずれも format_string, value ... を渡してメッセージ出せる
#[ignore = "not yet implemented"]
実際には test もつくので
code:test_ignore.rs
fn check_hoge() {...}
$ cargo test -- --ignored で ignore だけ実行する
#[should_panic] 属性
#[should_panic(expected = "message")]
テストメソッドが Result<T, E> を返してもよい
fn it_rowks() -> Result<(), String> { ... } など
非同期
async
code:async.rs
async fn hoge() -> () {
...
}
// ↓
fn hoge() -> impl std::future::Future<Output=()> {
async { ... }
}
Pin
自己参照する構造体を作りたい時につかう
構造体を関数から返すとムーブするので、構造体内部で参照先を維持できない
Pin は DerefMut できなくする
Cargo
cargo doc --open で doc comment などからページを作れる
VSCode Snippets
REPL
$ cargo install evcxr_repl
型見たい
code:print_type.rs
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>())
}
::<> ← turbofish
型パラメータを書きそうな場所に ::<T> を置く
let s: String = "str".into();
let s = Into::<String>::into("str");
let v = Vec::<u32>::new();
parse もそう
input.parse::<usize>()
deleference より参照を比較する方に倒す?
*ref == value か ref == &value どちらでも良い時だと後者にしがち?
値を比較しているんだッという気持ちで前者を好ましく思うけど、所有権が移動してしまう?
→ Copy が実装されていたらコピーが起きる
参照同士の比較はどう解決しているんだろ?
On-Stack Dynamic Dispatch
Box 使わなくてもよいパターン
monomorphises = 単相
code:dyn.rs
// These must live longer than readable, and thus are declared first:
let (mut stdin_read, mut file_read);
// We need to ascribe the type to get dynamic dispatch.
let readable: &mut dyn io::Read = if arg == "-" {
stdin_read = io::stdin();
&mut stdin_read
} else {
file_read = fs::File::open(arg)?;
&mut file_read
};
匿名? Tulple? 構造体
code:tup_struct.rs
struct Outer(Inner);
enum Inner {
A,
B,
}
let a = Outer(Inner::A)
a.0 //=> Inner::A
構造体のメンバの名前を指定しなければ数字でアクセス
無名の構造体 = Tuple
Inner 型を公開しないことでコンストラクタ経由を強制するパターン