20250818101134 learning rust

techrust

何度も挫けているが、rustの勉強をしたい。 挫けている理由の一つに、チュートリアル的なドキュメントを読んでいると疑問が大量に出てきて前に進めなくなる、というのがある気がする。気になったことをかたっぱしからここに書いて、調べつつ読み進めることにしてみる。

学習リソース

References

Playground

Rust Playground

気になったこと

プロジェクトのファイル構成について

rust bookにはこのように書かれている。 リファクタリングしてモジュール性とエラー処理を向上させる - The Rust Programming Language 日本語版

- プログラムを_main.rs_と_lib.rs_に分け、ロジックを_lib.rs_に移動する。
- コマンドライン引数の解析ロジックが小規模な限り、_main.rs_に置いても良い。
- コマンドライン引数の解析ロジックが複雑化の様相を呈し始めたら、_main.rs_から抽出して_lib.rs_に移動する。

これの出典はどこかにあるのだろうか? たとえば pythonだと決まっている 20240311211049 pythonのプロジェクト構成

cargoが作るのが基本、と考えればいいかもしれない。

これも参照。20250823152740 パッケージ(package)、 crate 、 モジュール(mod) という用語とそれぞれの定義(rust的な意味で)

naming convention

RFCで規定されている。

rfcs/text/0430-finalizing-naming-conventions.md at master · rust-lang/rfcs

識別子(identifier)として利用可能な文字

1.53以降ではunicodeが使える。[Rust] 円周率の表記 - 鴨川のはりねずみ

-# などの記号類は、 C/C++と同様、 _ 以外は使えない。これの最新の資料が見つけられないが、 古いバージョンのrustドキュメントには記載されている。 Identifiers - The Rust Reference

Raw identifiers というのを利用して、一部の予約語を使うこともできる。 Rust でキーワード(予約語)を識別子として使う方法 (raw identifier)と、その例外

アトリビュートって何?

20250823152929 アトリビュートって何

固定長のarrayある?

ある。 https://doc.rust-lang.org/std/primitive.array.html

グローバル変数はどうやって作るのか?

グローバル変数は簡単に作れない。

C++のtemplate部分特殊化に対応する機能/構文はある?

おそらく完全に一致するものは無い。

rust book list 10-10 のように限定的には可能。 cf. ジェネリックなデータ型 - The Rust Programming Language 日本語版

C++のconceptに対応する機能はある?

Trait Bound で実装できると思う

unionあるの?

ある。 Union s - The Rust Reference あんま推奨されないがC APIで必要になることも。

#[repr(C)] // あってもなくても結果は変わらなかった @ rustc 1.85.1 (4eb161250 2025-03-15)
union MyUnion {
    f1: u32,
    f2: f32,
}
 
fn main() {
    let u = MyUnion { f1: 42 };
 
    unsafe {
        println!("MyUnion.f1: {}", u.f1);
        println!("MyUnion.f2: {}", u.f2);
    }
}

とかやると、

MyUnion.f1: 42
MyUnion.f2: 0.000000000000000000000000000000000000000000059

PODのstructはCopy Traitを実装しなくてもcopyされるか?

されない。 PODという言い方なのだろうか。たぶん違う。Cの概念。 単純, 標準レイアウト, POD, およびリテラル型

// PODのstruct
struct MyStruct {
    v1: u32,
    v2: f32,
}
 
fn main() {
    let s = MyStruct { v1: 42, v2: 3.14 };
    println!("MyStruct.v1: {}", s.v1);
    println!("MyStruct.v2: {}", s.v2);
    
    let t = s; // この操作はmove i32とかだとcopyになる。
    println!("MyStruct.v1: {}", t.v1);
    println!("MyStruct.v2: {}", t.v2);
    //    moveされているので以下はコンパイルエラー
    //    println!("MyStruct.v1: {}", s.v1);
    //    println!("MyStruct.v2: {}", s.v2);
 
}

sliceと型強制

rust bookは 20250819 時点で、配列のスライスを取得するには

    let a = [1, 2, 3, 4, 5];
    let slice: &[i32] = &a[..]

のように書く、と書いてあるが、実際には

    let a = [1, 2, 3, 4, 5];
    let slice: &[i32] = &a

でも行けた。 rustc 1.85.1 (4eb161250 2025-03-15) この方が便利で、Stringなどをそのまま &str を引数に取る関数の引数として使用可能。この機能は「型強制という。 cf. 型強制

rustではVecやStrのような配列を扱う関数を書く時には、スライスを引数とすることが推奨されている。 cf. Rustで関数の仮引数の型をスライス(&[T], &str)にする | rs.nkmk.me

if letが直感的ではない

そう思った人がメンタルモデルを含めて詳しく書いてくれている。Rustの「if let」とは何なのか? - Qiita

bookの8.3を読んでいて思った。こういう時に便利。 ハッシュがあった時だけ表示する。とかが簡単に書ける。

use std::collections::HashMap;
fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
 
    let team_name = String::from("blue");
    if let Some(score) = scores.get(&team_name) {
        println!("Score for {}: {}", team_name, score);
   }
}
 

パッケージ(package)、 crate 、 モジュール(mod) という用語とそれぞれの定義(rust的な意味で)

20250823152740 パッケージ(package)、 crate 、 モジュール(mod) という用語とそれぞれの定義(rust的な意味で)

cargo new で workspace作れないの?

今(20250908) はできない。昔からある議論らしい。 activeなissue: Add `cargo new —workspace` to create a workspace · Issue #8365 · rust-lang/cargo

Traitのデフォルト実装を特殊化したケースの関数内から呼ぶことは可能か?

この人と同じことを期待したが、そのままではできない。Call default trait method impl from specialized impl

あるデータ型にどのようなTraitが実装されているかを調べる方法はあるのか?

そもそもRTTIあるのか?

おそらく無い(し、そのような実装はrust的では無い) r/rust - Reddit

downcast的なことは可能 Rust の std::any::Any トレイトを用いて安全に動的型付けっぽいことをやる - Qiita

println! などで使える placeholder {}

要調査 {:?} とか {:#?} とかある。 hexで表示したい、とか0詰めしたいとかの場合はどうしたらいいの?

ここにまとめられている。 std::fmt - Rust

ライフタイムを省略できるのは、どんな場合?

この機能は lifetime elision という。

どういう時に省略可能なのかは、 この説明がわかりやすかった。 Rustのライフタイム推論入門

book の ライフタイム省略 も参照。 ライフタイムで参照を検証する - The Rust Programming Language 日本語版

最初の規則は、参照である各引数は、独自のライフタイム引数を得るというものです。換言すれば、 1引数の関数は、1つのライフタイム引数を得るということです: `fn foo<'a>(x: &'a i32)`; 2つ引数のある関数は、2つの個別のライフタイム引数を得ます: `fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`; 以下同様。

2番目の規則は、1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入されるというものです: `fn foo<'a>(x: &'a i32) -> &'a i32`。

3番目の規則は、複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが`&self`や`&mut self`だったら、 `self`のライフタイムが全出力ライフタイム引数に代入されるというものです。 この3番目の規則により、必要なシンボルの数が減るので、メソッドが遥かに読み書きしやすくなります。

この規則を適用して、全部の引数と戻り値にライフタイムが設定できる場合は省略できる。

for loop with indexどうやるの?

enumerate(), zip() が使える。 Rust インデックス付きで Vec, イテレータをループ, 走査する方法

typedef あるの?

ある。 type MyInt = i32;

ただし、alias。別の型ではない。(そこもC/C++と同じ)

Does Rust have an equivalent of C’s typedef?

Boxっていつ使うの?

スマートポインタ全体的に謎いから整理したほうがいいかも。

【Rust】Boxの使いどころ

正直、使うケースは少ないと思います。
スタックに積めばいいものをわざわざヒープに確保する事は無駄です。

アンダースコアいつ使うの?

  • matchのデフォルトケース
let x = Some(5);
match x {
    Some(1) => println!("one"),
    Some(n) => println!("number: {}", n),
     _ => println!("anything else"), 
}
  • 未使用変数の先頭に付けると未使用警告が出ない
let _hoge = 0;
// 使わない

これは使うことになったら変数名を変更する必要があって、ちょっと微妙だと思った。

  • returnされた値を使わないから無視したい

といいつつ、このコードは警告も出ない。

fn add(x: i32, y: i32) -> i32 {
    x + y
}
 
fn main()
{
    _ = add(1, 2); //  _ は無くてもコンパイルエラーにならない
}
 

このくらいやると警告が出る warning: unused Result that must be used

fn add(x: i32, y: i32) -> Result<i32, String> {
    Ok(x + y)
}
 
fn main()
{
    add(1, 2); // 
}
  • タプルの使わない要素を無視
fn main()
{
    let tuple = (1, 2, 3);
    let (x, _, z) = tuple; // _ を y とかにすると未使用の警告が出る
    println!("{}, {}", x, z)
}
  • 使わない引数を無視 トレイトの実装とかでインターフェースを揃える時に必要そう
fn function(x: i32, _: i32) -> i32 {
    x // 2番目に名前を付けると警告が出る
}
  • コンパイラに推論を任せられるけど記述が必要な場合
    let numbers = (0..10).collect(); // これはできない。 error[E0283]: type annotations needed
    // collectで作れる データ構造がいっぱいあって、どれかわからない。
    
    // 例えばこのようにしろ、というヒントがエラーに表示される。 Vec であることまで伝えれば良い。 (VecDequeとかでもいい)
    let _numbers: Vec<_> = (0..10).collect();

ライフタイムでも使うことがある、とのこと。 Rust: 匿名ライフタイム - TAKOYAKING’s blog

未調査


個人の感想

所有権について

所有権の概念は、C++を使っている人は 「rustの変数は基本的にいつでもstd::unique_ptrの問題点を無くしたものを使っている」 と思うと分かりやすいかもしれない。 std::move とかを使いはじめてイライラしたことがあると、何をしたかったのか分かりやすい。

関数などの引数もunique_ptrだと思っておけば、「コピーは基本的にできない。参照渡しのみ」というのも個人的には理解しやすかった。むしろC++と比べたらsyntax sugarで書きやすくなっている。

  • C++でも参照渡しを基本にするべきだし、constの有無を丁寧にやった方が良い。この「やった方が良い」をやりやすくしているのがrust。

これを3回くらい読む。 所有権を理解する - The Rust Programming Language 日本語版

ヘッダファイルが無くて素晴しい

実装内容を1つのファイルに書けるのがとても良い。C++だと#includeで絶妙に循環参照を避けていたケースはどうやって運用すればいいのだろう、というのが若干気になるが、他の言語だと大抵1つのファイルに書いているので、問題となることは無さそう。「適切にファイル/モジュールを分割せよ」ということに尽きる。

Optionalが素晴しい

個人的にずっとboolは true, false, nil(無効) の3値で扱われるべきと思っていたので、それが実現されている点はとても良いと思う。 schemeだと nil は '() で代用されていた。

enumが素晴しい

enumが列挙だけではなく、型+値を保持できるのは便利。ステートマシンを書く時に便利だと思う。

matchが素晴しい

enum + match の世界観はアプリケーションロジックを書く時に便利だと思う。

クロージャ

クロージャなのにtrait名が Fn とかなのが謎い