Rustを書いてみる

出力
ログ出力: println!
マクロ
- 文字列以外は出力できない
- プレースホルダ
{}
を使うことで数値とかを埋め込める
println!("num is {}", 10); // num is 10

デバッグ用に変数を出力するdbg!
マクロもある。
この際所有権の関係(?)で、参照を渡す必要がある。
let msg: String = "Hello";
dbg(&msg);
&
をつけなかった場合、所有権がdbg!
に移るっぽい?

↑と思いきや、所有権が移る場合と移らない?場合がありそう。
String
型は移るが、&str
型やその他リテラルは移らない?

println!
のフォーマットはstd::fmt
のものと同じらしい。
また、プレースホルダにはいくつかの構文がある。
-
{?}
: デバッグ(dbg!
)と同じように出力する -
{引数名}
: 引数に名前をつけ、その名前で順序を決定する -
:
: フォーマット指定子と別のとを区切る
println!(
"{index}: {suit:?} {rank}",
index = i + 1,
suit = card.suit,
rank = card.rank
);
なお、Rustに他言語の名前付き引数はない模様。
じゃあこの構文は一体...?
詳細はドキュメントを参照。

変数を直接埋め込むこともできるらしい。
let num = 10;
println!("num is {num}"); // num is 10
ただしこれはprintln!
の機能なので、通常の変数宣言等に応用はできない。
どうして...

リテラル
- 数値(
i32
とかu32
とか)-
i
は符号付き(負の数OK) - メモリサイズに応じで
8
とか64
とかもある
-
- 文字(
char
):'
で囲む - 文字列:
"
で囲む-
&str
とString
があるらしい?
-

変数
let 変数名: 型名 = 初期値;
で宣言できる。
let num: u8 = 10;
変数はデフォルトで不変。
可変にするならmut
をつける。

入力を受け付け、それを数値に変換する:
// 入力を受け付ける
let mut ans_input = String::new();
std::io::stdin().read_line(&mut ans_input).unwrap();
// 数値にする
let ans_input = ans_input.trim().parse::<i32>().unwrap();

if
式
- 式なので変数に代入できる
- 条件式を囲む
()
は不要 -
dbg!
を条件式部分に入れる技がある
if dbg!(1 + 1 == 2) {/* 略 */}

if let
っていう、Scalaのunapply
みたいなやつもある。
Rustではこれをやるのに、
- パターンマッチング
if let
の2通りがあるみたい。

クレート
クレート=Rust版npmパッケージ
追加にはcargo add
を使う
cargo add rand
このコマンドはCargo.toml
に依存関係を追記する。
また、Cargo.lock
というロックファイルも作ってくれる。
追加したクレートはuse
で使える。
use rand::Rng;
// ...
dbg!(rand::rng().random_range(0..100));

制御構文
-
if
式: 前述 -
while
,loop
-
match
: パターンマッチ
JavaScriptにおけるfor
文はない。
Rustにおけるfor
はfor..in
(JSのfor..of
みたいなやつ)を指す。
for (n in 1..10) {/* ... */}

↑に出てきた1..10
はRange
。
これは1以上10未満、つまり1 ~ 9
を指す。
1 ~ 10を指したい場合、1..=10
と書ける。

列挙型
enum
を使うと列挙型を定義できる。
値は型名::列挙子
で取り出せる。
enum Suit {
Heart,
Diamond,
Spade,
Club,
}
let suit: Suit = Suit::Diamond;
Suit::Diamond
とかはSuit
型の値になるっぽい...?

#[derive(Debug, Clone, Copy, PartialEq)]
ってなんだろう?
多分dbg!
での出力に必要な何かっぽいんだけど

構造体
struct
を使って定義できる。
メンバーは名前: 型,
で定義する。
struct Card {
suit: Suit,
rank: i32,
}
インスタンスは構造体名 { メンバー }
で作る。
メンバーには.
を挟んでアクセスできる。
let card = Card {
rank: 8,
suit: Suit::Diamond,
};
dbg!(card.rank); // 8
メンバー名と同じ名前の変数がある場合:
let card = Card { rank, suit };

無名関数
map
みたいな関数を受け取るメソッドはRustにもある。
無名関数の構文: |引数1, 引数2...| 処理
処理に書いた値が戻り値になるっぽい。
処理部分はブロック{}
で囲むこともでき、その場合も最後に書いた値が戻り値になる。
// 同じみのマッピング: map
let numbers: Vec<usize> = input
.split_whitespace()
.map(|str| str.parse().unwrap())
.collect(); // Vecに変換する?
// 全てが条件を満たすか?: all
let flash = hand.iter().all(|c| c.suit == first_suit);

配列
配列は特定の型のオブジェクトの集合。
固定長で、連続したメモリ領域に格納される。
生成には[]
が使える。
各要素は,
で区切り、最後の;
の後に要素数を書ける。
let array: [i32; 1] = [0];
let array = [0; 500];

Vec
Vec
は長さを変えられる配列(コレクション)。
Vec::new()
で作成できるほか、別のオブジェクトのcollect
メソッドなどから作成する方法もある。
let vec: Vec<i32> = Vec::new();
let vec = Vec::<i32>::new();
可変(let mut
)の場合はpush
などのメソッドが使える。
let mut vec: Vec<i32> = Vec::new();
vec.push(14);
dbg!(vec.first().unwrap()); // 14

配列とVec
は、iter()
メソッドでイテレーターに変換?できる。
これはよくfor
ループで使う。
また、イテレーターのenumerate()
メソッドでは、要素のインデックスを一緒に取得できる。

Vec
を作成するときに別の変数を使った場合、その値の所有権はVec
にムーブする。
また、Vec
の要素の所有権はVec
自身が持っている。
let v = String::from("Hello");
let vec = vec![v]; // move
dbg!(v); // Error!
dbg!(vec);

/target
ディレクトリは消してもいい by ChatGPT
ディスク容量が心配だし、作り終わったやつから消していこうかな

関数
fn 関数名(引数) -> 戻り値の型 { 処理 }
で定義する。
呼び出しは関数名(引数}
。
fn hello(name: &str) -> &str {
println!("Hello, {name}!");
name // 引数のnameをそのまま返す
}
// ...
hello("world");
- 戻り値の型を省略した場合は
()
になる - 戻り値の型は推論されない
- 最後にセミコロン
;
なしで書いた値が戻り値になる(ブロックの仕様っぽい)

仮引数を可変にする場合、引数名の前にmut
をつける。
可変参照を受け取る場合、型部分に&mut
をつける。
fn add(mut a: f64, b: f64) {}
fn add(a: &mut f64, b: f64) {}

変数の不変参照は&
で取得できる。
可変参照は&mut
で取得できるが、変数が可変(let mut
)で宣言されている必要がある。
参照から実際の値を取得する=参照外し(デリファレンス?)には*
を使う。
fn add(a: &mut i32, b: i32) {
*a += b;
}
let mut a = 10;
add(&mut a, 3);

関数などの説明はJSDoc方式で書けるっぽい。
/** */
の中に説明を書く。

戻り値の値の所有権は、その戻り値を使うやつ(例: 変数)に移る。
スコープから抜けるから破棄されるというわけではない。
fn gen_vec() -> Vec<String> {
let vec = vec![String::from("abc")];
vec
}
// ...
let vec = gen_vec(); // vecに所有権が移る
dbg!(vec); // OK

関数定義の引数名の前につけるmut
は、引数自体を可変にするもの。
これはScalaのval
とvar
に似ているかも。
fn sample(mut a: i32) { // 引数aを可変にする
a += 5; // OK
}
このa
引数を変えても、関数の外には影響しない。
あくまでも関数内での、ローカル変数を変更するだけなイメージ。

逆に可変参照を示す&mut
は、可変の変数を扱うイメージ。
参照は変数のショートカットと考えると、ただlet mut
で宣言された変数を扱っているだけに見えてわかりやすい。
以下のコードでは、init
の所有権をvec
の参照が指す先の変数に渡している。
fn change(vec: &mut Vec<i32>, init: Vec<i32>) {
*vec = init; // 参照外しすると変数が出てくる
}
let mut vec = vec![1, 2, 3];
let init: Vec<i32> = vec![4, 5, 6];
change(&mut vec, init); // initに入ってるVecの所有権をvecに渡す
dbg!(vec); // OK

良さそうな記事を発見

自動で参照外ししてくれる場合がある。
例えばi32
型の+
演算子は、*
をつけなくてもつけてもOK。
fn add(x: &i32) {
// どっちでも動く
dbg!("{}", x + 1);
dbg!("{}", *x + 1);
}
また、メソッド呼び出し時のself
(JSのthis
?)は参照を受け取る。
そのため(&vec).method()
のように明示的に参照であることを示すこともできるが、冗長なためvec.method()
でOK。
つまり、メソッドの呼び出しは参照からでもいける。
fn add(x: &Vec<i32>) {
x.iter(); // 参照からメソッドを呼び出す
(&x).iter(); // これはいったい...?
}

所有権
- 値には所有権を持つ所有者が必ず1つある
- 変数は所有権を持てる
- 所有権の保持者は値を読み書きできる
- 他の変数は参照を借用(
borrow
)できる

可変参照がある場合、所有権の持ち主も値を変更できない。
これは、Rustが複数の箇所から書き込みが可能な状態を許さないため。
let mut x = 5;
let y = &mut x; // 可変参照を取得
x += 10; // yはまだ生きているのでエラー
*y += 10;
なお、以下はエラーが出なかった。
y
の使用箇所がもうないため?
let mut x = 5;
let y = &mut x; // 可変参照を取得
*y += 10;
x += 10; // yは今後使われないのでOK?

所有権はいくつかの方法でムーブ(移動)できる。
- 変数へ代入する
- 関数の実引数として使う
ムーブによって所有権を失った変数は、値への読み取りも書き込みもできなくなる。
スコープから抜けたなどで所有者を失った値は、メモリから解放される。
そして、Copy
トレイトを実装しているものはムーブではなくコピーが行われる。
例えばi32
などの型が挙げられる。

Copyトレイトを実装しているものはムーブではなくコピーが行われる
このため、例えば変数の代入を行っても所有権は移動しない。
let value = 42;
let tmp = value; // コピーが作成される
dbg!(value);
このvalue
に例えばVec
を入れるとエラーになる。

*
で参照外しを行っても、出てくるのは所有権ではなく変数へのショートカット。
参照外しした値を変数に入れて所有権を得てみようとした結果、エラーが出た。
fn use_vec(vec_ref: &Vec<String>) {
let value = *vec_ref; // 参照外し
}
エラー内容:
cannot move out of
*vec_ref
which is behind a shared reference
shared reference
は直訳すると共有参照だけど、不変参照を指してるっぽい?

ちなみにRustでも参照の参照は作れるっぽい。
その場合自動参照外しは再帰的に行われるっぽい。
let vec = gen_vec();
let vec_ref = &vec;
let vec_ref_ref = &vec_ref;
dbg!(vec_ref_ref); // ["abc"]

- 所有権は値に対して発生する
- 参照は変数に対して発生する
- 借用は所有者から参照を借りること

参照も値の一種なので、所有権という概念が発生する。
- 不変参照は
Copy
トレイトを実装している - 可変参照は
Copy
トレイトを実装していない
let a = 10; // immutable object
let a_ref = &a; // reference
let a_ref_copy = a_ref;
print!("{} {} {}", a, a_ref, a_ref_copy); // borrow check!! - OK

Copy
はマーカートレイトの一種。
マーカートレイトはコンパイラに指示を与えるためだけの空のトレイト。

スライス
スライスは対象から一部を切り出したビュー。
文字列や配列、Vec
からは[start..end]
という記法でスライスを作成できる。
スライスは基本的に参照型で、例えば&[f64]
のようになっている。
また、不変のスライスと可変のスライスがある。
型強制という仕組みによって、Vec
や配列はスライスに自動で変換可能。
fn use_slice(slice: &[i32]) {
// 普通の配列と同じように使える
dbg!(slice.len());
}
// ...
let vec = vec![0; 10]; // Vecを作成するマクロ
use_slice(&vec[0..5]);
use_slice(&vec); // 型強制

なお、&str
はString
のスライス型。
"
で作ったリテラルは&str
型になる。

mut
をつけないことによる制約はあまり意味ない?
let vec = vec![1, 2, 3];
let mut vec = vec;
change(&mut vec); // OK
JavaScriptのconst
な変数がlet
で再宣言できるような不思議な感覚。
そもそも変数の仕組みが全然違うからそりゃそうだけど...

mut
は値が変更可能か=&mut self
なメソッドが呼べるかにも影響する。
そんな重要なところが簡単に変えられるのは驚き。
だけど、その分JavaScriptみたいに関数によってオブジェクトが変更されるのか読み取りしかされないのかがわからないということは、不変参照と可変参照があるのでない。
TypeScriptでreadonly
とかと比べると、考え方の違いがある?

Vec
の要素の所有権はvec
自身が持つ。
要素の所有権はムーブできない。
let vec = vec![String::from("hoge"), String::from("fuga")];
let tmp = vec[0]; // cannot move out of index of `Vec<String>`
ただし、Copy
トレイトを実装している場合は、ムーブではなくコピーが発生する。
let vec = vec![1, 2, 3];
let tmp = vec[0]; // ムーブではなくコピー
dbg!(vec[0]); // ムーブではなくコピー

何も#derive
していない構造体の場合、メンバーの所有権はムーブできる。
let vec = Memory { slots: vec![] };
let tmp = vec.slots; // move
dbg!(&vec.slots); // borrow of moved value: `vec.slots`

Vec
の要素や構造体のメンバーへの参照も取得できる。
これも値への参照というよりは、メンバーへの参照を取得している感じ。
let mut vec = Memory { slots: vec![] };
let tmp = &mut vec.slots; // vec.slotsの参照を取得
*tmp = vec![1.0];
dbg!(&vec.slots); // [1.0]

借用されている(参照が作成されている)値の所有権はムーブできない。
そのため、所有権が変わったとき参照はどこを指すのか?という心配はない。

for
とif
で簡単に実装できるけど、それでもfind
を使ってみたかった
impl Memory {
fn get_slot(&self, name: &str) -> Option<f64> {
self.slots.iter().find(|s| s.0 == name).copied()
// copiedは参照外しみたいなやつ
}
fn get_slot_mut(&mut self, name: &str) -> Option<&mut f64> {
self
.slots
.iter_mut()
.find(|s| s.0 == name)
.map(|s| &mut s.1) // mapはOption型のメソッド
}
}

-
get_slot
:f64
の値を返す、読み取り用 -
get_slot_mut
:&mut f64
を返す、書き込みもする用
こんな設計が許されるのかは知らない。
参照外しの関係で置き換えが簡単じゃないっていうデメリットはある。

本ではeval_token
もメソッドにしてるけど、自己判断で普通の関数にしてみた。
ここではget_slot
メソッドを呼び出してる。
fn eval_token(token: &str, memories: &Memory) -> f64 {
if token.starts_with("mem") {
let slot_name = &token[3..];
memories.get_slot(slot_name).unwrap_or(0.0)
} else {
token.parse::<f64>().unwrap()
}
}
この設計がいいのか悪いのかは知らない。

if let
、ネストが少なくなっていいかもしれない
比較用match
:
match slot {
Some(slot) => {
*slot += prev;
print_output(*slot);
}
None => {
self.slots.push((slot_name.to_string(), prev));
print_output(prev);
}
}
if let Some(slot) = slot { // 分解
*slot += prev;
print_output(*slot);
} else {
self.slots.push((slot_name.to_string(), prev));
print_output(prev);
}

本ではeval_tokenもメソッドにしてるけど、自己判断で普通の関数にしてみた。
「メモリがトークンに紐づく値を評価して返す」のはなんだかおかしな話です。
By 本
そう思ってたから変えてみたけど、やっぱりそうだったのか...
自分の設計を肯定された気分で嬉しい

メソッド
基本
- 構造体に関連する機能群のこと
-
impl 構造体名 {}
で定義する - 各メソッドは
fn メソッド名() {}
という感じで普通の関数のように定義できる
self
について
- 自身を表す変数で、引数に型なしで書く
- 所有権 or 不変参照 or 可変参照が欲しいのかで、
self
or&self
or&mut self
を使い分ける -
self
がないメソッド(他言語のstatic
みたいなやつ)もある
struct Memory {
slots: HashMap<String, f64>,
}
impl Memory {
// ファクトリみたいなやつ
fn new() -> Self {
Self { // Self = Memory
slots: HashMap::new(),
}
}
// 自身のslotsを参照する
fn get_slot(&self, name: &str) -> Option<f64> {
self.slots.get(name).copied()
}
}

HashMap
キーバリューのペアのコレクション。JavaScriptのMap
みたいなやつ。
ジェネリック型で、HashMap<Key, Value>
形式。
-
get(key) -> Option
: キーから値を取得する -
insert(key, value)
: ペアを追加する -
entry(key)
:Entry
オブジェクトを取得する

Entry
HashMap
の各ペアを示す?型。
同名の型はstd::colections::HashMap
以外にも存在するので混同に注意。
entry
メソッドで取得でき、値は以下のどちらか。
-
Entry::Occupied
: キーがHashMap
にある-
get()
: 不変参照を取得する -
get_mut()
: 可変参照を取得する -
remove()
: 削除する?
-
-
Entry::Vacant
: ない-
insert(value)
: そのキーに値を追加する
-

match hash_map.entry(key) {
Entry::Occupied(mut entry) => {
*entry.get_mut() += num; // 参照を取得できるので代入できる
}
Entry::Vacant(entry) => {
entry.insert(num); // 値を追加する
}
}

5章終わり!