Closed19

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

ピン留めされたアイテム
shuntakashuntaka

よく使うコマンド

モジュールの追加

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

勘違いしやすい挙動

Cargo.toml
[dependencies]
rand = "0.8.4"
  • randの0.8.4の完全一致ではない
  • 0.8.4 - 0.9.0のうち最新を取得する
Cargo.toml
[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
shuntakashuntaka

まず the bookやって、なんか作ってみるか。意識することはこれ。僕は何度も挫折しているから分かるんだ。ゼロから学ぶRustをまた続きから始めてもいいな。

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

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

shuntakashuntaka

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://github.com/shuntaka9576/dotfiles/commit/52c7e7f8b81cee3b39b61f35c27ae35f3f465af4

shuntakashuntaka

標準ライブラリを調べる方法

短縮リンクがある。https://docs.rs/std

https://docs.rs/std

検索ボックスに入力

docs.rsのTips

  • スクロールしても、sキーを押せば検索ボックスに再度フォーカスが当たる
shuntakashuntaka

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

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

shuntakashuntaka
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も同じ理論で可能。

shuntakashuntaka
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

shuntakashuntaka

定数にして、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
}

shuntakashuntaka

定数の実体は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
shuntakashuntaka

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

shuntakashuntaka

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

shuntakashuntaka

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

shuntakashuntaka

この 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);

shuntakashuntaka

この挙動素晴らしいなと思う。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");

このスクラップは2024/12/27にクローズされました