Rust の練習帳 メモ

- book: https://www.oreilly.co.jp/books/9784814400584/
- official repo: https://github.com/kyclark/command-line-rust
- my repo: https://github.com/dak2/rust-command-line-tool
- with .devcontainer

match expression

& keyword で参照
引数に & keyword を取ると借用
=> コンパイラが正確にライフタイムを解析してメモリ安全を保証する(=安全に解放できる)

From trait
複数のエラー型をカプセル化して単一のエラー型を返せる

書籍に unwrap
出てくるから普通に使ってたけど、値が取れなかった場合に panic になるから unwrap_or
, unwrap_or_else
, or unwrap_or_default
を使った方が良いのか

Struct の field に省略記法が使えるのか
let bytes = matches.value_of("bytes")
.map(parse_positive_int)
.transpose()
.map_err(|e| format!("illegal byte count -- : {}", e))?;
Ok(Config {
files: matches.values_of_lossy("files").unwrap(),
lines: lines.unwrap(),
bytes,
})

let lines = matches.value_of("lines")
.map(|line| parse_positive_int(line))
.transpose()
.map_err(|e| format!("illegal line count -- : {}", e))?;
↓map に直接 function 渡せるのか。原理が分かっていない
let lines = matches.value_of("lines")
.map(parse_positive_int)
.transpose()
.map_err(|e| format!("illegal line count -- : {}", e))?;

? 演算子
- Ok(x) の場合は値をアンラップして x を返す
- Err(x) の場合は Err(From::from(e)) を返す

アンダースコア
- コンパイラに値を使いたくないことを伝える
- match アームではあらゆるケースにマッチするワイルドカード

Option 型の Some から値を取り出した上で評価できる
下の例だと config.bytes が Option<usize> であり、None でないことを確認できたら、num_bytes に usize を代入してブロックスコープ内で利用可能
if let Some(num_bytes) = config.bytes {
let mut handle = file.take(num_bytes as u64);
let mut buffer = vec![0; num_bytes];
let bytes_read = handle.read(&mut buffer)?;
print!(
"{}",
String::from_utf8_lossy(&buffer[..bytes_read])
)
}

return 要らないのか

fn gen_config(
files: Vec<String>,
mut lines: bool,
mut words: bool,
mut bytes: bool,
mut chars: bool,
) -> MyResult<Config> {
if lines {
lines = true;
words = false;
bytes = false;
chars = false;
}
if words {
lines = false;
words = true;
bytes = false;
chars = false;
}
if bytes {
lines = false;
words = false;
bytes = true;
chars = false;
}
if chars {
lines = false;
words = false;
bytes = false;
chars = true;
}
if !lines && !words && !bytes && !chars {
lines = false;
words = false;
bytes = false;
chars = true;
}
Ok(Config {
files,
lines,
words,
bytes,
chars,
})
}
fn gen_config(
files: Vec<String>,
lines: bool,
words: bool,
bytes: bool,
chars: bool,
) -> MyResult<Config> {
let (lines, words, bytes, chars) = match (lines, words, bytes, chars) {
(true, _, _, _) => (true, false, false, false),
(_, true, _, _) => (false, true, false, false),
(_, _, true, _) => (false, false, true, false),
(_, _, _, true) => (false, false, false, true),
_ => (false, false, false, true),
};
Ok(Config {
files,
lines,
words,
bytes,
chars,
})
}
パターンマッチでこのように書き換えられるのか

5章くらいまで Copilot と進めてきて、意識的に Option を unwrap したり question operator を使ったりと Result 周りに慣れてきた感覚がある

impl
は struct
に対する振る舞いを定義

- &str
- &[u8]
- 普変で固定長
- &[u8]
- String
- Vec<u8>
- 伸長可能
- Vec<u8>
動的に生成する場合は String を利用する

iter()
で Iter
に直してからイテレートするのか
let arr = [1, 2, 3, 4]; // [i32: 4]
arr.iter() // Iter
arr.iter().all(|&n| ....)

format! で桁数指定ができる
format!("{:04}", 42); // => "0042" with leading zeros

ライフタイム
その参照が有効になるスコープ
ダングリング参照
参照するつもりだったデータ以外のデータを参照してしまう
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `x` does not live long enough
(エラー[E0597]: `x`の生存期間が短すぎます)
--> src/main.rs:7:17
|
7 | r = &x;
| ^^ borrowed value does not live long enough
| (借用された値の生存期間が短すぎます)
8 | }
| - `x` dropped here while still borrowed
| (`x`は借用されている間にここでドロップされました)
9 |
10 | println!("r: {}", r);
| - borrow later used here
| (その後、借用はここで使われています)
error: aborting due to previous error
For more information about this error, try `rustc --explain E0597`.
error: could not compile `chapter10`.
To learn more, run the command again with --verbose.
=> 変数 x は借用されているがブロックを抜ける時にドロップされてしまっているにも関わらず、r で参照しようとしている

`static アノテーションをつけると、参照が指し示す値がプログラムの実行中に渡って生き続ける

所有権(ownership)
プログラムがリソースを所有する権利
- 各値それぞれに1つ所有権を持っている
- スコープから外れたら値は破棄される
ムーブ(move)
所有権が移動すること
#![allow(unused)]
fn main() {
let s = String::from("hello"); // sがスコープに入る
takes_ownership(s); // sの値が関数にムーブされ s の所有権を失う
println!("let s: {}", s)
}
fn takes_ownership(some_string: String) { // some_stringがスコープに入る。
println!("some_string: {}", some_string); // スコープを抜けると some_string が drop
}
上記コードはエラーになる
-
takes_ownership
関数に s の所有権がムーブされる -
takes_ownership
関数のスコープを抜けると所有権を持っていた値が破棄(drop)されてメモリから解放される - 呼び出し元の
main
関数では、すでに s は破棄されており参照できない

借用(borrow)
所有権をムーブさせずに参照だけさせたい場合に使う
#![allow(unused)]
fn main() {
let s = String::from("hello"); // sがスコープに入る
takes_ownership(&s); // & により変数を貸し出す
println!("let s: {}", s) // s は借用させただけなので所有権を保持していることから参照できる
}
fn takes_ownership(some_string: &String) { // some_stringがスコープに入る。
println!("some_string: {}", some_string); // some_string の所有権は持っていないのでスコープ外となっても drop されない
}
-
takes_ownership
関数にs
を貸し出す(&
operator) -
takes_ownership
関数ではs
の所有権は無く借用しているだけなので、関数のスコープを抜けても drop されない - 呼び出し元の
main
関数はs
の所有権を持っているので、takes_ownership
を抜けたあとでもprintln!
でs
を参照できる-
s
のライフタイムはmain
関数のスコープ内になっている
-
借用を使うということは、まだそのスコープ内で変数を使う可能性があるということを示してるとも言えそう

所有権の解像度が上がった

参照外し(dereference)
参照されたコードから値を取得する
参照は実質的にポインタなので、ポインタの指す値を取得すること
fn main() {
let a = 1;
let b = &a;
println!("{}", *b);
}
=> 1
println に渡すケースだと、* つけなくても値が出力されるから、参照を渡しても内部で dereference されてそうな気がする
fn main() {
let a = 1;
let b = &a;
println!("{}", b);
}

Copy トレイトを実装していない型だと dereference がエラーになるよう
下記の例だと、String は Copy トレイトを実装していないが、usize は Copy トレイトを実装している
Copy を実装している型は dereference 時に move ではなく copy になるためエラーにならない。
Copy を実装していない String は dereference 時に所有権を move しようとしているとみなしてエラー
だから、error[E0507]: cannot move out of *b which is behind a shared reference
と怒られる
fn two() {
let a = String::new();
// reference to a is created. a can still be used, though exactly how it can be used depends on context
let b = &a;
// dereferencing can't move out of a, we can read the value but not move it
*b; // error[E0507]: cannot move out of `*b` which is behind a shared reference
}
fn three() {
let a = 0usize;
// reference to a is created. a can still be used, though exactly how it can be used depends on context
let b = &a;
// dereferencing is allowed because the type of a implements Copy, which means its impossible to move out of a. Attempts to move out of a will always result in a new copy of the value
*b; // Creates a new usize value copied from a.
}

6章の uniq まで実装してみた
あとは大体同じような実装だったのでここら辺で終わる