Rustを学ぶ(1年ぶり4度目)

よく使うコマンド
モジュールの追加
cargo-editを使わない場合(非推奨)
コマンド
# 0.8.4 - 0.9.0のうち最新を取得する
cargo add rand@0.8.4
# 0.8.4を取得する
cargo add rand@=0.8.4
or
toml変更後
cargo build
勘違いしやすい挙動
[dependencies]
rand = "0.8.4"
- randの0.8.4の完全一致ではない
- 0.8.4 - 0.9.0のうち最新を取得する
[dependencies]
rand = "=0.8.4"
- randの0.8.5固定
-
cargo update
しても固定されているので、不可 -
=
を除去すれば、最新に更新される
$ cargo update
Updating crates.io index
Locking 1 package to latest compatible version
Updating rand v0.8.3 -> v0.8.5
Removing rand_hc v0.3.2
note: pass `--verbose` to see 1 unchanged dependencies behind latest

まず the bookやって、なんか作ってみるか。意識することはこれ。僕は何度も挫折しているから分かるんだ。ゼロから学ぶRustをまた続きから始めてもいいな。
- 作ることを目的にしない(あなたの知っている言語で書いた方が100倍早いになってしまうから)
- なぜ100倍早いかを考察するようにする。それはその言語が隠蔽している箇所になりうるから
- 分からないことは動かすように対処するのではなく、理論に立ち返り、学びながら仮説をたてて解決していく
- 同じ問題と遭遇したとき、試行錯誤を最小限に済むようにその場しのぎのおまじないで動いて思考を拒否することをしない
- おまじない(理解していないマクロなど)が多くなってしまうことに萎えない。時間をかけて意味を理解する

このスクラップでは追加で実施した環境構築のメモを追記していく

cargo-editのインストール
試行錯誤したやつ
$ brew install openssl@1.1 pkg-config
$ /opt/homebrew/Cellar/openssl@1.1/1.1.1w/bin/openssl version
OpenSSL 1.1.1w 11 Sep 2023
$ export PATH="/opt/homebrew/Cellar/openssl@1.1/1.1.1w/bin:$PATH"
$ which openssl
/opt/homebrew/Cellar/openssl@1.1/1.1.1w/bin/openssl
$ openssl version
OpenSSL 1.1.1w 11 Sep 2023
$ sudo rm -rf /Library/Developer/CommandLineTools
$ xcode-select --install
Macだと不要っぽい。。Linuxの場合かも...
# install
brew install FiloSottile/musl-cross/musl-cross
rustup default stable-aarch64-apple-darwin
結局 rm -rf ~/.cargoして、rustupを入れ直した。
cargo install cargo-edit
cargo upgrade
でhangする問題があったが、以下のコマンド直った
クレートを更新する際に、create.io周りとやりとりがあるんだが、それをhttpsからgit経由に変更している

標準ライブラリを調べる方法
短縮リンクがある。https://docs.rs/std
検索ボックスに入力
docs.rsのTips
- スクロールしても、
s
キーを押せば検索ボックスに再度フォーカスが当たる

マクロは別コードを生成。短く書くためのショートハンド的なもの。

とてもわかりやすい

use std::collections::HashMap;
use std::string::String;
fn main() {
let name = String::from("foo");
let map = greet_map(1, &name);
println!("map: {:?}", map);
}
// 特に理由がない限り借用がよいnameはStringではなく&strがよい
// (理由): 所有権がうつるため、他のところで使いにくくなる
fn greet_map(id: i32, name: &str) -> HashMap<i32, &str> {
let mut map = HashMap::new();
let message = format!("Hello, {}", name);
map.insert(id, message.as_str());
map
}
$ cargo run
Compiling shoyuuken-movie v0.1.0 (/Users/shuntaka/repos/github.com/shuntaka9576/rust-playground/shoyuuken-movie)
error[E0515]: cannot return value referencing local variable `message`
--> src/main.rs:22:5
|
20 | map.insert(id, message.as_str());
| ------- `message` is borrowed here
21 |
22 | map
| ^^^ returns a value referencing data owned by the current function
For more information about this error, try `rustc --explain E0515`.
error: could not compile `shoyuuken-movie` (bin "shoyuuken-movie") due to 1 previous error
これは通る。messageはgreet_mapスコープで実体が消えてしまう。ゆえにmapそのまま返そうとするとエラーになる。nameは実態がmainスコープなので大丈夫。
use std::collections::HashMap;
use std::string::String;
fn main() {
let name = String::from("foo");
let map = greet_map(1, &name);
println!("map: {:?}", map);
}
// 特に理由がない限り借用がよいnameはStringではなく&strがよい
// (理由): 所有権がうつるため、他のところで使いにくくなる
fn greet_map(id: i32, name: &str) -> HashMap<i32, &str> {
let mut map: HashMap<i32, &str> = HashMap::new();
let message = format!("Hello, {}", name);
map.insert(id, name);
map
}

use std::collections::HashMap;
use std::string::String;
fn main() {
let name = String::from("foo");
let map = greet_map(1, &name);
println!("map: {:?}", map);
}
// 特に理由がない限り借用がよいnameはStringではなく&strがよい
// (理由): 所有権がうつるため、他のところで使いにくくなる
fn greet_map(id: i32, name: &str) -> HashMap<i32, &str> {
let mut map: HashMap<i32, &str> = HashMap::new();
map.insert(id, name.trim());
map
}
name.trim()
は$strになり、スタックに積まれているptrが先頭にあるスペース分先頭アドレスが進んで、lenもスペース分少なくなる。trimは伸びることがない。基本少なくなるので、これで可能。substring
も同じ理論で可能。

use std::collections::HashMap;
use std::string::String;
fn main() {
let name = String::from(" foo ");
let res = greet_map(1, &name);
println!("res: {:?}", res)
}
fn greet_map(id: i32, name: &str) -> HashMap<i32, &str> {
let mut m: HashMap<i32, &str> = HashMap::new();
m.insert(id, name.trim());
m
}
main関数の中身を一部f関数に入れるとコンパイルエラーが発生する。これはnameのライフタイムがf関数に閉じるため、mainまでのスコープがないため。
use std::collections::HashMap;
use std::string::String;
fn main() {
let res = f();
println!("res: {:?}", res)
}
fn f() -> HashMap<i32, &str> {
let name = String::from(" foo ");
let res = greet_map(1, &name);
res
}
fn greet_map(id: i32, name: &str) -> HashMap<i32, &str> {
let mut m: HashMap<i32, &str> = HashMap::new();
m.insert(id, name.trim());
m
}
$ cargo run
Compiling shoyuuken-movie v0.1.0 (/Users/shuntaka/repos/github.com/shuntaka9576/rust-playground/shoyuuken-movie)
error[E0106]: missing lifetime specifier
--> src/main.rs:10:24
|
10 | fn f() -> HashMap<i32, &str> {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
10 | fn f() -> HashMap<i32, &'static str> {
| +++++++
help: instead, you are more likely to want to return an owned value
|
10 | fn f() -> HashMap<i32, String> {
| ~~~~~~
error[E0515]: cannot return value referencing local variable `name`
--> src/main.rs:14:5
|
12 | let res = greet_map(1, &name);
| ----- `name` is borrowed here
13 |
14 | res
| ^^^ returns a value referencing data owned by the current function
Some errors have detailed explanations: E0106, E0515.
For more information about an error, try `rustc --explain E0106`.
error: could not compile `shoyuuken-movie` (bin "shoyuuken-movie") due to 2 previous errors

定数にして、staticライフタイムをつけることで通るようになる。
-fn f() -> HashMap<i32, &str> {
- let name = String::from(" foo ");
+fn f() -> HashMap<i32, &'static str> {
+ let name = " foo ";
use std::collections::HashMap;
use std::string::String;
fn main() {
let res = f();
println!("res: {:?}", res)
}
fn f() -> HashMap<i32, &'static str> { // <- 👈 変更点
let name = " foo "; // <- 👈 変更点
let res = greet_map(1, &name);
res
}
fn greet_map(id: i32, name: &str) -> HashMap<i32, &str> {
let mut m: HashMap<i32, &str> = HashMap::new();
m.insert(id, name.trim());
m
}

定数の実体はrodata(read-only data)にあり、ヒープでもスタックでもない。静的領域にいるため。staticライフタイムをつけることで、mainへ渡せる。
バイナリをhexdumpするとその定数が書き込まれている。
$ hexdump -C target/debug/shoyuuken-movie | grep "foo"
0003da00 0a 20 20 66 6f 6f 20 00 00 00 00 00 00 00 00 00 |. foo .........|
これでrodataに書き込まれていることがわかる。
readelf -a target/debug/shoyuuken-movie

まとめると変数をヒープで確保したスコープより外のスコープで、その値を参照/借用するな

所有権は結局double freeを防ぐ仕組みみたいに覚えておくといいのかな
スコープを抜けると、free/dropが走る。ある変数をある変数に代入した場合、両方の変数は同じポインタを指しているわけで、2回freeすることになるから、所有権で防ぐみたいな

動画の所有権とライフタイムはメモリの使い方に名前をつけただけっていうのがかなりしっくりきた 🦊

この mut sだが、これは文字列リテラルを変更できるという意味ではない。単にsはmutableなので別の参照を持てるくらいに考えたほうが良い。sは静的メモリへの参照しかない。
let mut s = "Hello, world";
let r_s = s.replace("world", "aa");
println!("r_s: {}", r_s);
let s2 = "foo";
s = s2;
println!("s: {}", s);

この挙動素晴らしいなと思う。mutableでない場合は再代入は出来ない。
TSはconst宣言したオブジェクトのフィールドの値は可変だからね。。readonlyつけないと...
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// user1がmutableなら変更可能
user1.email = String::from("anotheremail@example.com");

これもやらんと