Rustやる(1年ぶり3度目) ~ ゼロから学ぶRust編 ~
過去挫折しまくってるので、今回でなんとかハマりたいところ
今回はこちらを写経することにする(レーサー本は2回くらい写経したし...)
環境構築系まとめ
頻出コマンド
rustup系
rustup update # バージョン更新
rustup component add rustfmt # フォーマッタ追加
rustup component add rust-src # LSP(rust-analyzer)追加
ユースケース
# プロジェクトの作成
cargo init s003 --bin
cd s003
cargo run
coc.nvimでログが出る rust-analyzer入れてるのに何でだろう
[coc.nvim] RLS is no longer available as of Rust 1.65.
Consider migrating to rust-analyzer instead.
See https://rust-analyzer.github.io/ for installation instructions.
Press ENTER or type command to continue
わかった coc-rls
からcoc-rust-analyzer
に変える必要があった。
:CocInstall coc-rust-analyzer
静的解析だとコンパイルエラーも一部実行時にしか分からないものがあるので、cargo-watch
を入れることにした。
cargo install cargo-watch
cargo watch -x run
用語系まとめ
算術演算子 | 説明 |
---|---|
>> | 右シフト |
<< | 左シフト |
名称 | 宣言 |
---|---|
不変参照型(immutable reference type) | & |
可変参照型(mutable reference type) | &mut |
不変ポインタ型(?) | * |
可変ポインタ型(?) | *mut |
-
Rustはデフォルトで破壊的代入(destructive assignment)が不可
-
参照変数の前に*を付けると、参照の指している先の値となり、この操作は参照はずし(dereference)と呼ぶ
-
ソースコードレベルでの検査を静的検査(static analysis)
-
実行時の検査(dynamic analysis)
-
回復不能なエラーをpanic(パニック)と呼ぶ
文字列系
- 文字列型には、String型と&str型がある
- &str
- 文字列スライス
トラブルメモ
配列への範囲外アクセスはコンパイルエラーなんだけど、静的解析側で気付けないのか...
エラーメッセージ
$ cargo run
Compiling s002 v0.1.0 (/Users/shuntaka/repos/github.com/shuntaka9576/rust-zero-kara-manabu/s002)
warning: unused variable: `s`
--> src/main.rs:42:13
|
42 | let s: &[u32] = &arr[1..3];
| ^ help: if this is intentional, prefix it with an underscore: `_s`
|
= note: `#[warn(unused_variables)]` on by default
warning: value assigned to `n` is never read
--> src/main.rs:56:17
|
56 | let mut n: u64 = 100; // nを破壊的代入可能として宣言し、100を代入
| ^
|
= help: maybe it is overwritten before being read?
= note: `#[warn(unused_assignments)]` on by default
error: this operation will panic at runtime
--> src/main.rs:38:9
|
38 | arr[5];
| ^^^^^^ index out of bounds: the length is 4 but the index is 5
|
= note: `#[deny(unconditional_panic)]` on by default
error: this operation will panic at runtime
--> src/main.rs:44:9
|
44 | arr[5];
| ^^^^^^ index out of bounds: the length is 4 but the index is 5
warning: `s002` (bin "s002") generated 2 warnings
error: could not compile `s002` (bin "s002") due to 2 previous errors; 2 warnings emitted
データ型まとめ
種類 | データ型 |
---|---|
コレクション | LinkedList, VecDeque, Vec |
マップ | HashMap, BTreeMap |
セット | HashSet, BTreeSet |
マルチスレッド
手法 | 説明 |
---|---|
spawn,join | スレッドの生成はspawn, スレッドの終了はjoinで行う |
所有権
名称 | 説明 |
---|---|
ムーブセマンティクス | 関数呼び出しや代入時、消費された変数は、関数内や代入先の変数に所有権が移動する。このような動作のこと |
コピーセマンティクス | Copyトレイトを実装している型は、ムーブセマンティクスを使わず、値をコピーして代入や関数呼び出しをこなう。コピーして、代入や関数呼び出しをこおなう動作のこと |
ライフタイム
名称 | 説明 |
---|---|
字句ライフタイム(lexical lifetime) | 字句の並びからライフタイムを決定する |
非字句ライフタイム(non-lexical lifetime) | 意味的な解釈をしてライフタイムが決定される |
impl
キーワードの使い方
-
impl TypeName {...}
特定の型(構造体や列挙型)に対してメソッドを実装する場合
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
-
impl<T> TypeName<T> {...}
ジェネリック型の実装
ジェネリックな構造体に対する実装が結構覚え辛い記法をしている気がする。特にimpl<T>
というシンタックスを書く必要がある点など
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
-
impl TypeName for TraitName {...}
トレイトの実装
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
何も見ずに、Iteratorの実装を複数回やっているんだけど、この関数のジェネリクスにライフタイムだけ書く方法をよく忘れる。
本質を理解してないからなんだけど、、
fn iter<'a>(&'a self) -> ListIter<'a, T> {
ListIter { elm: self }
}
Mul<Output = T> + Copy
の部分をどこに書くかというところかな...
fn square<T>(x: T) -> T where T: Mul<Output = T> + Copy { x * x }
fn square<T: Mul<Output = T> + Copy>(x: T) -> T { x * x }
並行プログラミングのトレイト制約
トレイト | 説明 |
---|---|
std::maker::Sync |
複数のスレッドからアクセス可能 |
std::maker::Send |
複数のスレッド間でデータを受け渡し可能 |
スマートポインタ(参照を持つ賢いポインタ)
型名 | 説明 |
---|---|
std::sync::Arc |
スレッド間でデータの共有が可能 |
std::rc::Rc |
1スレッド内のみで、データ共有が可能 |
ドキュメントテストいいなぁ、Rustこういうところ親切だよね
/// my_func は私独自の関数です。
///
/// # 利用例
///
/// ```
/// use markdwon::my_func;
/// let n = my_func().unwrap();
/// ```
pub fn my_func() -> Option<u32> {
Some(100)
}
cargo test
偶数の乱数を返すコードなんだけど、最下位ビットだけ0のu32の2進最大値とAND演算して偶数にしてるのカッコいいな..
pub fn rand_even() -> u32 {
rand::random::<u32>() & !1
}
take 関数
use std::mem::take
let mut n = Some(10);
let v = take(&mut n); // vにnの値を代入した後、nに初期値のNoneを代入
println!("n = {:?}, v = {:?}", n, v);
// => n=None, v=Some(10)
Rustには所有権があり、普通に代入すると以降はnにアクセス不可
takeは所有権の取得と初期化を同時に行うため、所有権取得後もアクセス可能
代入後もその値を利用する場合、コンパイルエラーにならないようにするための関数と理解した。
項目6.3. stackのデータ型は、(Vec配列, Vec配列)のタプルとなっている
#[derive(Debug)]
pub enum AST {
Char(char),
Plus(Box<AST>),
Star(Box<AST>),
Question(Box<AST>),
Or(Box<AST>, Box<AST>),
Seq(Vec<AST>),
}
fn main() {
let mut stack = Vec::new();
let mut prev = Vec::new();
let mut prev_or: Vec<AST> = Vec::new();
prev.push(AST::Char('a'));
stack.push((prev, prev_or));
for v in stack.iter() {
println!("{:?}", v)
}
}
([Char('a')], [])
6章の正規表現と生成されるコードを書いておく
記号 | 意味 |
---|---|
? | 直前の文字が0回 or 1回 |
+ | 直前の文字が1回以上 |
expr | code |
---|---|
abc? | 0 char a 1 char b 2 split 3 4 3 char c 4 match |
abc+ | 0 char a 1 char b 2 char c 3 split 2 4 4 match |
メモ:abc?がabbでマッチするか確認
Rustのchanellの説明で、Goの思想が引用されている。
Do not communicate by sharing memory; instead, share memory by communicating
メモリを共有することで通信しようとしないこと
Instead of explicitly using locks to mediate access to shared data, Go encourages the use of channels to pass references to data between goroutines.
This approach ensures that only one goroutine has access to the data at a given time.
(訳)共有メモリ上のデータアクセス制御のために明示的なロックを使うよりは、Goではチャネルを使ってゴールーチン間でデータの参照結果をやり取りすることを推奨しています。
このやり方によって、ある時点で多くても1つのゴールーチンだけがデータにアクセスできることが保証されます。
送信機と受信機があって、必ず受信機のスレッドで変更するからデータアクセスが1つになるってことかな
tx
は送信機でTransmit eXchangで、rx
は受信機Received eXchangeの略で、利用されることが多いっぽい。
型自体は、Sender<T>
とReceiver<T>
という形になる。
let (worker_tx, worker_rx) = channel();
7章めっちゃ面白いけど、じっくりやりたいから8章のデバック先やるのもありだな