Closed23

Rustやる(1年ぶり3度目) ~ ゼロから学ぶRust編 ~

shuntakashuntaka

環境構築系まとめ

頻出コマンド

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
shuntakashuntaka

用語系まとめ

算術演算子 説明
>> 右シフト
<< 左シフト
名称 宣言
不変参照型(immutable reference type) &
可変参照型(mutable reference type) &mut
不変ポインタ型(?) *
可変ポインタ型(?) *mut
  • Rustはデフォルトで破壊的代入(destructive assignment)が不可

  • 参照変数の前に*を付けると、参照の指している先の値となり、この操作は参照はずし(dereference)と呼ぶ

  • ソースコードレベルでの検査を静的検査(static analysis)

  • 実行時の検査(dynamic analysis)

  • 回復不能なエラーをpanic(パニック)と呼ぶ

文字列系

  • 文字列型には、String型と&str型がある
  • &str
    • 文字列スライス
shuntakashuntaka

トラブルメモ

配列への範囲外アクセスはコンパイルエラーなんだけど、静的解析側で気付けないのか...

エラーメッセージ
$ 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

shuntakashuntaka

データ型まとめ

種類 データ型
コレクション LinkedList, VecDeque, Vec
マップ HashMap, BTreeMap
セット HashSet, BTreeSet
shuntakashuntaka

マルチスレッド

手法 説明
spawn,join スレッドの生成はspawn, スレッドの終了はjoinで行う
shuntakashuntaka

所有権

名称 説明
ムーブセマンティクス 関数呼び出しや代入時、消費された変数は、関数内や代入先の変数に所有権が移動する。このような動作のこと
コピーセマンティクス Copyトレイトを実装している型は、ムーブセマンティクスを使わず、値をコピーして代入や関数呼び出しをこなう。コピーして、代入や関数呼び出しをこおなう動作のこと
shuntakashuntaka

ライフタイム

名称 説明
字句ライフタイム(lexical lifetime) 字句の並びからライフタイムを決定する
非字句ライフタイム(non-lexical lifetime) 意味的な解釈をしてライフタイムが決定される
shuntakashuntaka

implキーワードの使い方

  1. impl TypeName {...} 特定の型(構造体や列挙型)に対してメソッドを実装する場合
#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}

impl Rectangle {
  fn area(&self) -> u32 {
    self.width * self.height
  }
}
  1. impl<T> TypeName<T> {...} ジェネリック型の実装
    ジェネリックな構造体に対する実装が結構覚え辛い記法をしている気がする。特にimpl<T>というシンタックスを書く必要がある点など
ジェネリックな構造体Point<T>に対してメソッドを実装
struct Point<T> {
  x: T,
  y: T,
}

impl<T> Point<T> {
  fn x(&self) -> &T {
    &self.x
  }
}
  1. 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)
  }
}
shuntakashuntaka

何も見ずに、Iteratorの実装を複数回やっているんだけど、この関数のジェネリクスにライフタイムだけ書く方法をよく忘れる。
本質を理解してないからなんだけど、、

        fn iter<'a>(&'a self) -> ListIter<'a, T> {
            ListIter { elm: self }
        }
shuntakashuntaka

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 }
shuntakashuntaka

並行プログラミングのトレイト制約

トレイト 説明
std::maker::Sync 複数のスレッドからアクセス可能
std::maker::Send 複数のスレッド間でデータを受け渡し可能

スマートポインタ(参照を持つ賢いポインタ)

型名 説明
std::sync::Arc スレッド間でデータの共有が可能
std::rc::Rc 1スレッド内のみで、データ共有が可能
shuntakashuntaka

ドキュメントテストいいなぁ、Rustこういうところ親切だよね

/// my_func は私独自の関数です。
///
/// # 利用例
///
/// ```
/// use markdwon::my_func;
/// let n = my_func().unwrap();
/// ```
pub fn my_func() -> Option<u32> {
    Some(100)
}
上記のハイフンで囲まれたRustコードのテストが可能
cargo test
shuntakashuntaka

偶数の乱数を返すコードなんだけど、最下位ビットだけ0のu32の2進最大値とAND演算して偶数にしてるのカッコいいな..

pub fn rand_even() -> u32 {
    rand::random::<u32>() & !1
}
shuntakashuntaka

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は所有権の取得と初期化を同時に行うため、所有権取得後もアクセス可能
代入後もその値を利用する場合、コンパイルエラーにならないようにするための関数と理解した。

shuntakashuntaka

項目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')], [])
shuntakashuntaka

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
shuntakashuntaka

https://go.dev/blog/codelab-share

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つになるってことかな

shuntakashuntaka

txは送信機でTransmit eXchangで、rxは受信機Received eXchangeの略で、利用されることが多いっぽい。
型自体は、Sender<T>Receiver<T>という形になる。

    let (worker_tx, worker_rx) = channel();
shuntakashuntaka

7章めっちゃ面白いけど、じっくりやりたいから8章のデバック先やるのもありだな

このスクラップは6ヶ月前にクローズされました