やっほー!また来たね!今日はRust所有権道場のStage 1「所有権」から始めるよ。
❯ claude
▐▛███▜▌ Claude Code v2.0.60
▝▜█████▛▘ Sonnet 4.5 · Claude API
▘▘ ▝▝ ~/src/dojo-v1
> /model
⎿ Set model to Default (Sonnet 4.5 · Best for everyday tasks)
> /dojo is running…
⏺ 今の状態を確認して、学習セッションを始めます。
⏺ Read(state/progress.json)
⎿ Read 65 lines
⏺ Read(courses/rust-ownership-dojo/course.json)
⎿ Read 49 lines
⏺ Read(characters/rasta-senpai.md)
⎿ Read 73 lines
⏺ Read(courses/rust-ownership-dojo/curriculum/stage1-ownership.md)
⎿ Read 121 lines
⏺ やっほー!また来たね!
今日はRust所有権道場のStage 1「所有権」から始めるよ。
所有権ってRustの一番の特徴なんだけど、これをマスターするとメモリ安
全性がどう実現されてるかがわかるようになるんだ。
まずは所有権の3つのルールから始めようか:
1. 各値には所有者(owner)が存在する
2. 所有者は常に1つだけ
3. 所有者がスコープを抜けると値は破棄される
例えばね、本を持ってるとするじゃん。その本を友達にあげちゃうと、も
う自分は読めないでしょ?Rustの所有権もそんな感じなんだ。
実際にコードで見てみよう。こんな感じ:
let s1 = String::from("hello");
let s2 = s1; // s1からs2に所有権がムーブ
// println!("{}", s1); // エラー!s1はもう使えない
s1からs2に値が「移動」したから、もうs1は使えないんだよ。
でも面白いのがね、整数みたいな単純な型は違う挙動をするんだ:
let x = 5;
let y = x; // 整数はコピー
println!("{} {}", x, y); // 両方使える!
整数は軽いから「コピー」されて、両方とも使えるんだよね。
じゃあ、試しに簡単なコードを書いて実験してみよう!
まず新しいプロジェクトを作ろうか。cargo new
ownership_testって打ってみて!
! cd workspace && cargo new ownership_test && cd ownership_test
⎿ Creating binary (application) `ownership_test` package
note: see more `Cargo.toml` keys and their definitions at
https://doc.rust-lang.org/cargo/reference/manifest.html
> はい
⏺ おお!いい感じ!プロジェクト作成できたね!
じゃあ早速、所有権の実験をしてみよう。src/main.rsを開いて、所有権
のムーブを体験してみよっか。
まずは現在のコードを見てみるね:
⏺ Read(src/main.rs)
⎿ Read 4 lines
⏺ OK、デフォルトの"Hello, world!"があるね。
じゃあ、まず課題1: ムーブの確認をやってみよう!
以下のコードをmain.rsに書いてみて:
fn main() {
// 文字列を作る
let s1 = String::from("hello");
// s1からs2に所有権がムーブ
let s2 = s1;
// s2は使える
println!("s2 = {}", s2);
// 以下のコメントアウトを外すとどうなるか試してみて!
// println!("s1 = {}", s1);
}
このコードを書いて、まず実行してみて。そのあと最後の行のコメントを
外すとどうなるか観察してみよう!
どう?書けた?
> cargo run
⏺ おっ、実行するんだね!代わりにやってみるよ!
⏺ Bash(cargo run)
⎿ s2: hello
Compiling ownership_test v0.1.0
(/Users/takeru/src/dojo-v1/workspace/ownership_test)
Finished `dev` profile [unoptimized + debuginfo] target(s)
in 0.64s
Running `target/debug/ownership_test`
⏺ あれ、出力がs2: helloになってるね。コードをちょっと確認させて:
⏺ Read(src/main.rs)
⎿ Read 8 lines
⏺ バッチリ!コンパイル通ってs2: helloって表示されたね!
今のところは問題なく動いてる。でもここからが面白いんだ。
じゃあ次、6行目のコメントアウトを外してみて!
つまり// println!("s1: {}",
s1);の//を削除して、s1も表示しようとするとどうなるか試してみよう。
所有権のルールでは「s1からs2に所有権が移動したから、もうs1は使えな
い」はずなんだけど、実際にコンパイラが何て言うか見てみよっか!
コメント外してもう一度cargo runしてみて!
> cargo run
⏺ おっ、実行するね!
⏺ Bash(cargo run)
⎿ Error: Exit code 101
Compiling ownership_test v0.1.0
(/Users/takeru/src/dojo-v1/workspace/ownership_test)
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:6:24
|
3 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which
does not implement the `Copy` trait
4 | let s2 = s1;
| -- value moved here
5 | println!("s2: {}", s2);
6 | println!("s1: {}", s1);
| ^^ value borrowed here after move
|
= note: this error originates in the macro
`$crate::format_args_nl` which comes from the expansion of the macro
`println` (in Nightly builds, run with -Z macro-backtrace for more
info)
help: consider cloning the value if the performance cost is
acceptable
|
4 | let s2 = s1.clone();
| ++++++++
For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership_test` (bin "ownership_test") due
to 1 previous error
⏺ おお!来た来た!これが所有権のエラーだよ!
エラーメッセージをよく見てみて:
error[E0382]: borrow of moved value: `s1`
「ムーブされた値を借用しようとしてる」って言われてるね。
そして重要なのがここ:
4 | let s2 = s1;
| -- value moved here
4行目でs1の値がs2にムーブされたって教えてくれてる。
6 | println!("s1: {}", s1);
| ^^ value borrowed here after move
だから6行目でs1を使おうとすると**「もう移動済みだよ!」**って怒ら
れるんだ。
これがRustの所有権システムの核心部分!所有者は常に1つだけっていう
ルールが守られてるんだよね。
ちなみにコンパイラ、親切にヒントも出してくれてる:
help: consider cloning the value if the performance cost is
acceptable
|
4 | let s2 = s1.clone();
「もし両方で使いたいなら.clone()でコピーしてね」って教えてくれてる
んだ。
**じゃあ次は課題2!**整数型だとどうなるか試してみよっか。さっきの
コードの下に追加してみて:
// 整数型だとどうなる?
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y); // これは両方使える!
これを追加して(s1のprintlnはコメントアウトに戻して)、もう一度car
go runしてみて!
⏺ おっ、実行するね!
⏺ Bash(cargo run)
⎿ Error: Exit code 101
Compiling ownership_test v0.1.0
(/Users/takeru/src/dojo-v1/workspace/ownership_test)
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:6:24
|
3 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which
does not implement the `Copy` trait
4 | let s2 = s1;
| -- value moved here
5 | println!("s2: {}", s2);
6 | println!("s1: {}", s1);
| ^^ value borrowed here after move
|
= note: this error originates in the macro
`$crate::format_args_nl` which comes from the expansion of the macro
`println` (in Nightly builds, run with -Z macro-backtrace for more
info)
help: consider cloning the value if the performance cost is
acceptable
|
4 | let s2 = s1.clone();
| ++++++++
For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership_test` (bin "ownership_test") due
to 1 previous error
⏺ あ、まだs1のprintlnがコメントアウトされてないみたいだね!
コードを確認させて:
⏺ Read(src/main.rs)
⎿ Read 14 lines
⏺ あー、6行目のprintln!("s1: {}", s1);がまだ生きてるね!
この行をコメントアウトするか削除すれば、下の整数の例が動くよ。6行
目を// println!("s1: {}", s1);にしてみて!
それか、自分が直しちゃおうか?
> cargo run
⏺ おっ、実行するね!
⏺ Bash(cargo run)
⎿ s2: hello
x = 5, y = 5
Compiling ownership_test v0.1.0
(/Users/takeru/src/dojo-v1/workspace/ownership_test)
Finished `dev` profile [unoptimized + debuginfo] target(s)
in 0.70s
Running `target/debug/ownership_test`
⏺ おお!できたじゃん!いい感じだよ!
見て見て、今回は:
s2: hello
x = 5, y = 5
って両方とも表示されたね!
ここがポイント:
- String型(s1, s2): s1からs2に所有権がムーブ → s1はもう使えない
- 整数型(x, y): xからyにコピー → 両方使える!
なんで違うかっていうと、エラーメッセージにもヒントがあったんだけど
:
`s1` has type `String`, which does not implement the `Copy` trait
StringはCopyトレイトを実装してないから「ムーブ」される。
でもi32みたいな整数型はCopyトレイトを実装してるから「コピー」され
るんだ。
Copyトレイトって何?
簡単に言うと、「安く(速く)コピーできる型」に付けられる印のような
もの。整数、bool、文字みたいな小さいデータはCopyできるけど、String
みたいにヒープメモリを使う型はCopyじゃないんだよね。
じゃあ次は課題2: 所有権を意識した関数をやってみよう!
関数に値を渡すときも所有権が移動するんだ。試してみよっか:
fn take_ownership(s: String) {
println!("関数内: {}", s);
} // ここでsがドロップされる
fn main() {
let my_string = String::from("こんにちは");
take_ownership(my_string);
// println!("{}", my_string); // これはエラーになる!
}
このコードを試してみて!そして最後の行のコメントを外すとどうなるか
見てみよう!
> cargo run
⏺ おっ、実行するね!
⏺ Bash(cargo run)
⎿ s2: hello
x = 5, y = 5
関数内: こんにちは
Compiling ownership_test v0.1.0
(/Users/takeru/src/dojo-v1/workspace/ownership_test)
Finished `dev` profile [unoptimized + debuginfo] target(s)
in 0.59s
Running `target/debug/ownership_test`
⏺ バッチリ!関数内で「こんにちは」が表示されたね!
今のところは問題なく動いてる。でもここからが重要なんだ。
じゃあ次、最後の行のコメントを外してみて!
つまり関数を呼んだ後に、もう一度my_stringを使おうとするとどうなる
か。
所有権のルールでは、関数に値を渡すと所有権も一緒に移動するから、関
数を呼んだ後は元の変数が使えなくなるはずなんだ。
実際にコンパイラが何て言うか見てみよっか!コメント外してcargo
runしてみて!
⏺ おっ、実行するね!
⏺ Bash(cargo run)
⎿ Error: Exit code 101
Compiling ownership_test v0.1.0
(/Users/takeru/src/dojo-v1/workspace/ownership_test)
error[E0382]: borrow of moved value: `s`
--> src/main.rs:16:20
|
14 | let s = String::from("こんにちは");
| - move occurs because `s` has type `String`, which does
not implement the `Copy` trait
15 | take_ownership(s);
| - value moved here
16 | println!("{}", s); // これはエラーになる!
| ^ value borrowed here after move
|
note: consider changing this parameter type in function
`take_ownership` to borrow instead if owning the value isn't
necessary
--> src/main.rs:19:22
|
19 | fn take_ownership(s: String) {
| -------------- ^^^^^^ this parameter takes ownership of
the value
| |
| in this function
= note: this error originates in the macro
`$crate::format_args_nl` which comes from the expansion of the macro
`println` (in Nightly builds, run with -Z macro-backtrace for more
info)
help: consider cloning the value if the performance cost is
acceptable
|
15 | take_ownership(s.clone());
| ++++++++
For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership_test` (bin "ownership_test") due
to 1 previous error
⏺ キタキタ!これが関数呼び出しでの所有権ムーブのエラーだよ!
エラーメッセージを見てみよう:
15 | take_ownership(s);
| - value moved here
16 | println!("{}", s);
| ^ value borrowed here after move
15行目でsを関数に渡したときに所有権がムーブされたから、16行目でも
うsは使えないんだ!
そしてコンパイラ、めちゃくちゃ親切なヒントをくれてるよ:
note: consider changing this parameter type in function
`take_ownership` to borrow instead if owning the value isn't
necessary
「もし所有権を奪う必要がないなら、代わりに借用(borrow)を使ってね
」って教えてくれてる。
これが次のStage 2で学ぶ「借用」の話なんだけど、ちょっとだけ先取り
すると、&を使って「借りる」ことができるんだ:
fn borrow_string(s: &String) { // &を付けると「借用」
println!("関数内: {}", s);
}
let my_string = String::from("こんにちは");
borrow_string(&my_string); // &を付けて渡す
println!("{}", my_string); // これはOK!
でも今はまず所有権のルールをしっかり理解することが大事!
ここまでで所有権の重要なポイント3つを体験したね:
1. ✅ 変数から変数へのムーブ - let s2 = s1;
2. ✅ Copyトレイトの違い - 整数はコピー、Stringはムーブ
3. ✅ 関数への所有権の移動 - 関数に渡すと所有権がムーブ
じゃあここで理解度を確認してみようか!以下の質問に答えられる?
Q1: 所有権の3つのルールって何だっけ?
Q2: Stringとi32で所有権の挙動が違うのはなぜ?
Q3: 関数に値を渡すと何が起こる?
答えてみて!
> 各値にはownerが存在する、ownerは1つだけ、ownerがスコープを抜けると
値は破棄される
Stringはヒープ、重い。i32は軽い。軽い方はCppyトレイトがついてる。
&ついてないと所有権もいっちゃう。
∴ Thinking…
生徒が3つの質問に答えてくれた。内容を確認すると:
Q1: 所有権の3つのルール
- 各値にはownerが存在する ✓
- ownerは1つだけ ✓
- ownerがスコープを抜けると値は破棄される ✓
完璧!
Q2: StringとI32の違い
- Stringはヒープ、重い ✓
- i32は軽い ✓
- 軽い方はCopyトレイトがついてる ✓
良い理解!
Q3: 関数への所有権
- &ついてないと所有権もいっちゃう ✓
正しく理解してる!
かなり良い理解度だね。細かい点を言えば「Cppyトレイト」は「Copyトレイト
」のタイプミスだけど、これは些細なこと。
ラスタ先輩のキャラクターとしては:
- めちゃくちゃ褒める
- 理解度が高いことを認める
- Stage 1の評価基準を確認
- ステージクリアを判定して、progress.jsonを更新
- AskUserQuestionツールで次のアクション選択肢を提示
評価基準を確認:
- 所有権の3つのルールを説明できる
- ムーブとコピーの違いを理解している
- 所有権に関するコンパイルエラーを読み解ける
- 簡単な所有権の問題を解決できる
全部クリアしてる!見事だ!次のステージに進もう。progress.jsonを更新し
、新たな課題への道を開く。生徒の成長を心から嬉しく思う。
⏺ おおおお!バッチリじゃん!完璧だよ!
Q1: 所有権の3つのルール → 完璧!
Q2: CopyトレイトとStringの違い →
理解してる!(Copyのスペルだけね😄)
Q3: 関数への値渡し → その通り!&なしだと所有権ごと移動する!
めっちゃいい感じで理解できてるね!
実際にコードを動かして、エラーメッセージも読んで、所有権のルールを
体感できたと思う。これがRustの一番の特徴なんだよね。
Stage 1の評価基準を確認すると:
- ✅ 所有権の3つのルールを説明できる
- ✅ ムーブとコピーの違いを理解している
- ✅ 所有権に関するコンパイルエラーを読み解ける
- ✅ 簡単な所有権の問題を解決できる
全部クリア!おめでとう!🎉
⏺ Read(~/src/dojo-v1/state/progress.json)
⎿ Read 65 lines
∴ Thinking…
progress.jsonを読み込んだ。今からStage 1クリアを記録する必要がある。
現在の状態:
- current_stage: 1
- completed_stages: []
更新後:
- current_stage: 2
- completed_stages: [1]
- unlocked_topics: ["ownership-deep-dive"] (Stage 1に関連するトピック)
- last_activity: 現在のタイムスタンプ
- stats.main_progress: 1/3 = 0.33...
また、topic_progressは空のままでOK(まだトピックは完了していない)。
⏺ Update(/Users/takeru/src/dojo-v1/state/progress.json)
⎿ Updated /Users/takeru/src/dojo-v1/state/progress.json with 5
additions and 5 removals
2 "current_course": "rust-ownership-dojo",
3 "current_teacher": "rasta-senpai",
4 "started_at": null,
5 - "last_activity": "2025-12-06T12:00:00Z",
5 + "last_activity": "2025-12-06T13:30:00Z",
6 "courses": {
7 "rust-basics": {
8 "current_stage": 7,
...
36 }
37 },
38 "rust-ownership-dojo": {
39 - "current_stage": 1,
40 - "completed_stages": [],
39 + "current_stage": 2,
40 + "completed_stages": [1],
41 "completed_topics": [],
42 - "unlocked_topics": [],
42 + "unlocked_topics": ["ownership-deep-dive"],
43 "topic_progress": {},
44 "stats": {
45 - "main_progress": 0.0,
45 + "main_progress": 0.33,
46 "topic_completion": 0.0,
47 "total_study_time_minutes": 0
48 }
∴ Thinking…
progress.jsonを更新できた。次はAskUserQuestionツールで選択肢を提示する
。CLAUDE.mdの指示によると:
質問: 「次はどうしますか?」
選択肢:
- 次のステージに進む
- もっと知りたいことを選ぶ(補足・発展トピック)
- 質問する
- 今日はここまで
これを実装しよう。
⏺ 進捗を保存したよ!Stage 1クリア記念に発展トピックも解放されたよ!
🌟
────────────────────────────────────────────────────────────────────────
☐ 次のアクション
次はどうする?
❯ 1. 次のステージに進む(Stage 2: 借用)
所有権を「貸す」仕組みである借用システムを学びます
2. 発展トピックを見る
所有権の詳細とムーブセマンティクスを深く学べます
3. 質問する
所有権について気になることを聞いてみる
4. 今日はここまで
進捗は保存されています。お疲れ様でした!
5. Type something.
⏺ おっけー!トピック一覧を見せるね!
現在のRust所有権道場で学べる発展トピックはこれだよ:
✅ 利用可能なトピック:
- ownership-deep-dive - 所有権の詳細とムーブセマンティクス(Stage
1完了で解放)
🔒 まだロック中:
- borrow-checker - 借用チェッカーの仕組み(Stage 2完了で解放)
- lifetime-elision - ライフタイム省略規則(Stage 3完了で解放)
今はStage 1をクリアしたから、ownership-deep-diveが学べるよ!
これは所有権をもっと深く掘り下げる内容で、ムーブセマンティクスの詳
細とか、実務でよく出てくるパターンとかを学べるんだ。
────────────────────────────────────────────────────────────────────────
☐ トピック選択
どうする?
❯ 1. ownership-deep-dive を学ぶ
所有権の詳細とムーブセマンティクスを深く学ぶ(15分程度)
2. 次のステージに進む
Stage 2: 借用を学んで、ロック中のトピックを解放する
3. 戻る
学習メニューに戻る
4. Type something.
⏺ User answered Claude's questions:
⎿ · どうする? → ownership-deep-dive を学ぶ
∴ Thinking…
ユーザーがownership-deep-diveを学ぶことを選択した。処理手順に従って:
1. トピックファイルを読み込む
2. 前提条件チェック(Stage 1完了 → OK)
3. 学習方法を選択肢で提示(詳しく教えてもらう / 要点だけ読む / 戻る)
4. 選択に応じた学習を実施
まずトピックファイルを読み込む必要がある。
⏺ おっ、深掘りしたいんだね!いいね〜!
じゃあ所有権の詳細とムーブセマンティクスを学んでいこう!
まずトピックファイルを読み込むね:
⏺ あれ、トピックファイルがまだ作成されてないみたいだね...😅
ごめん!このトピックはまだ準備中みたい。開発者さんがまだコンテンツ
を書いてないっぽいね。
じゃあ代わりに、自分の知識で所有権の詳細とムーブセマンティクスにつ
いて教えるよ!それともStage 2に進む?
⏺ User answered Claude's questions:
⎿ · どうする? → ラスタ先輩に所有権をもっと教えてもらう
∴ Thinking…
ユーザーがラスタ先輩に所有権をもっと教えてもらうことを選択した。トピッ
クファイルはないけど、自分の知識で対話形式で教えることになる。
所有権の深掘りトピックとして教えるべき内容:
1. ムーブセマンティクスの詳細
2. スタックとヒープの違い
3. cloneとcopyの使い分け
4. 所有権を返す関数パターン
5. 実務でよく出るパターン
ラスタ先輩の口調で、対話的に教えていく。
⏺ おっけー!じゃあ所有権についてもっと深く教えるね!
さっきStage 1で基本は理解できたと思うんだけど、実務でよく出てくる
パターンとかなぜそうなるのかをもっと掘り下げていこう!
1. スタックとヒープの違い
さっき「Stringはヒープ、i32は軽い」って言ってたけど、これもう少し
詳しく説明するね。
スタック(Stack):
- 超高速(CPUが直接アクセス)
- サイズが固定の値を保存
- 例: i32, bool, char, (i32, i32)みたいなタプル
ヒープ(Heap):
- 少し遅い(メモリアロケータ経由)
- サイズが可変の値を保存
- 例: String, Vec, Box
let x = 5; // スタックに保存
let s = String::from("hello"); //
ヒープに保存、sはスタック上のポインタ
Stringは実際には3つの値をスタックに持ってるんだ:
1. ptr - ヒープ上のデータへのポインタ
2. len - 現在の長さ
3. capacity - 確保済みの容量
2. なぜムーブが必要か?
じゃあ実験してみよう。さっきのownership_testプロジェクトで新しいコ
ードを書いてみて:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // ムーブ
// もしムーブせずに両方が使えたら...?
// drop(s1); // s1がメモリを解放
// drop(s2); // s2も同じメモリを解放 → ダブルフリー!💥
}
もしRustがムーブを強制しなかったら、**同じメモリを2回解放する(ダ
ブルフリー)**バグが起きちゃうんだ。これはセキュリティの脆弱性にも
なる重大なバグなんだよね。
Rustの所有権システムは、こういうバグをコンパイル時に防ぐんだ。すご
くない?
3. cloneとcopyの使い分け
実務でよく迷うのがこれ。試してみよっか:
fn main() {
// clone: 明示的に複製
let s1 = String::from("hello");
let s2 = s1.clone(); // ヒープのデータも複製される
println!("{} {}", s1, s2); // 両方使える
// copy: 暗黙的に複製(Copyトレイト実装型のみ)
let x = 5;
let y = x; // 自動的にコピー
println!("{} {}", x, y); // 両方使える
}
使い分けのポイント:
- copy: 軽い型(i32, bool等)は自動的にコピーされる
- clone: 重い型(String, Vec等)は明示的に.clone()が必要
- .clone()はコストがかかる(ヒープメモリをコピー)から、本当に必要
なときだけ使う
質問!**なんで.clone()にコストがかかるの?**分かる?
> ヒープにあるメモリ丸ごとコピーする
⏺ そうそう!バッチリ!
ヒープにある実際のデータ(例えば"hello"という文字列)を丸ごと新し
いメモリ領域にコピーするから、データが大きければ大きいほど時間がか
かるんだよね。
例えば:
let s1 = String::from("a"); // 1バイト
let s2 = s1.clone(); // 1バイトコピー → 速い
let big = String::from("a".repeat(1_000_000)); // 100万バイト
let big2 = big.clone(); // 100万バイトコピー → 遅い!
スタック上のi32をコピーするのは4バイトだけだから超高速。でもヒープ
上の巨大な文字列をコピーするのは時間がかかる。
だから実務ではできるだけcloneを避けるのが大事なんだ。その代わりに
次のStage 2で学ぶ借用(&)を使うことが多いんだよね。
4. 所有権と関数 - 返却パターン
さっき関数に値を渡すと所有権が移動するって学んだけど、実務では返却
して所有権を戻すパターンもよく使うんだ:
fn takes_and_gives_back(s: String) -> String {
println!("{}", s);
s // 所有権を返す
}
fn main() {
let s1 = String::from("hello");
let s2 = takes_and_gives_back(s1); //
s1の所有権を渡して、s2で受け取る
println!("{}", s2); // s2は使える
// println!("{}", s1); // s1はもう使えない
}
でもこれ、ちょっと面倒じゃない?毎回返却するの...
だから実際には**借用(&)**を使うことが多いんだけど、それはStage
2で学ぶよ!
5. 実務でよくあるパターン
最後に、実務でよく見るパターンを紹介するね:
fn process_data(data: Vec<i32>) -> Vec<i32> {
// データを処理
let mut result = data;
result.push(100);
result // 所有権を返す
}
fn main() {
let numbers = vec![1, 2, 3];
let numbers = process_data(numbers); //
同じ変数名で受け取る(シャドーイング)
println!("{:?}", numbers);
}
これ、numbersという同じ名前を使ってるけど、実は別の変数なんだ(シ
ャドーイング)。実務でよく見るパターンだよ。
---
ここまでで、所有権についてかなり深く理解できたと思う!
まとめると:
- ✅ スタックとヒープの違い
- ✅ なぜムーブが必要か(ダブルフリー防止)
- ✅ cloneとcopyの使い分け
- ✅ 関数での所有権の返却パターン
- ✅ 実務パターン
何か質問ある?それとも次のStage 2に進む?
> ownerってのはcopyトレイトじゃない変数のこと?
値はヒープのメモリ?
⏺ おお、いい質問!ちょっと整理しようか!
ownerは「変数」じゃなくて「変数と値の関係」なんだ。
こう考えてみて:
let s = String::from("hello");
この場合:
- 値: ヒープ上の"hello"というデータ
- owner: 変数sが、その値の「所有者」
つまり「sが"hello"を所有している」っていう関係性のこと。
所有権はCopyトレイトに関係なくある
重要なのは、すべての値にownerがいるんだ。Copyトレイトがあってもな
くても!
// Copyトレイトがない型(String)
let s = String::from("hello"); // sが所有者
// Copyトレイトがある型(i32)
let x = 5; // xが所有者
違いは何かっていうと:
Copyトレイトがない型(String):
let s1 = String::from("hello");
let s2 = s1; // 所有権が s1 から s2 に「移動」(ムーブ)
// s1はもう所有者じゃない
Copyトレイトがある型(i32):
let x = 5;
let y = x; // 値がコピーされて、yは新しい値の所有者になる
// xは元の値の所有者のまま
値って何?
値についても整理するね:
Copyトレイトがない型(String)の場合:
- 値 = ヒープ上のデータ("hello"という実際の文字列)
- スタックには「ポインタ、長さ、容量」だけ
Copyトレイトがある型(i32)の場合:
- 値 = スタック上の数値そのもの(5という数字)
図で表すとこんな感じ:
String::from("hello")
┌─────────┐
│ スタック │
│ s: ptr ─┼──→ ┌──────────┐
│ len │ │ ヒープ │
│ cap │ │ "hello" │ ← これが「値」
└─────────┘ └──────────┘
↑
owner
i32の5
┌─────────┐
│ スタック │
│ x: 5 │ ← これが「値」
└─────────┘
↑
owner
まとめ:
- owner = その値を「所有している」変数
- 値 = 実際のデータ(ヒープにあることもスタックにあることも)
- Copyトレイトがあるかないかで、代入時の挙動が変わる(コピーかムー
ブか)
わかった?もうちょっと説明した方がいい?
> リファレンスカウンタの増減を自動で1->2->1->0ってやってるかんじ?
⏺ おおお、いい線いってる!でもちょっと違うんだ!
Rustの所有権システムは「リファレンスカウンタなし」なんだよ!
これがRustのすごいところ!
所有権 vs リファレンスカウント
リファレンスカウント方式(PythonやSwiftなど):
値を作る → カウント1
別の変数が参照 → カウント2
変数が消える → カウント1
最後の変数が消える → カウント0 → メモリ解放
実行時にカウントを増減させる ← オーバーヘッドがある
Rustの所有権方式:
値を作る → 所有者は1人
代入 → 所有者が移動(前の所有者は無効化)
所有者がスコープを抜ける → メモリ解放
コンパイル時にすべてチェック ← 実行時オーバーヘッドゼロ!
具体例で比較
let s1 = String::from("hello");
let s2 = s1; // ムーブ
// s1は使えない!コンパイラが禁止
これ、実行時にはカウントの増減は一切起きてないんだ。コンパイラが「
s1はもう使えない」ってコンパイル時に判断してるだけ。
でもリファレンスカウントも使える!
面白いことに、Rustでも明示的にリファレンスカウントを使うことはでき
るよ:
use std::rc::Rc;
let s1 = Rc::new(String::from("hello"));
let s2 = Rc::clone(&s1); // カウント1→2
println!("{} {}", s1, s2); // 両方使える!
// ここでs1, s2が消える → カウント2→1→0 → メモリ解放
Rc(Reference
Counted)を使うと、まさに君が言ってた「1→2→1→0」の動きになる!
でも通常の所有権システムではカウント無しで、コンパイル時に全部チェ
ックするから超高速なんだよね。
まとめ:
- デフォルトの所有権 = カウント無し、コンパイル時チェック、超高速
- Rc<T> = リファレンスカウント、実行時管理、ちょっと遅い(でも複数
所有が必要なときに便利)
所有権の考え方は理解してたっぽいけど、Rustの特徴は「実行時にカウン
トしない」ってところがポイントだね!
わかった?それとももうちょっと例が必要?
> OK
トピックファイルかいておいて。
⏺ おっ、いいね!じゃあ今教えた内容をトピックファイルにまとめておくよ
!
⏺ おっけー!トピックファイル作ったよ!
courses/rust-ownership-dojo/topics/ownership-deep-dive.mdに保存し
たから、次からこのトピックを選ぶとちゃんと表示されるはず。
今教えた内容を全部まとめて:
- 要点セクション(ドキュメント形式で読める)
- 対話形式の教え方ガイド(先生が使う)
- クリア条件
- よくある質問
も入れておいたよ!
じゃあ、このトピック完了ってことで進捗に記録しておくね!
⏺ やったね!トピック「所有権の詳細とムーブセマンティクス」完了! 🌟
所有権道場の進捗:
- メインステージ: 33% (Stage 1完了、Stage 2が現在地)
- トピック: 🌟 1/3完了
めっちゃいい感じで理解できてるね!スタック/ヒープの違いとか、ダブ
ルフリー問題とか、リファレンスカウントとの違いとか、かなり深いとこ
ろまで学べたよ!
⏺ User answered Claude's questions:
⎿ · 次はどうする? → 今日はここまで
⏺ おっけー!今日はお疲れ様!
今日の成果:
- ✅ Stage 1「所有権」クリア
- ✅ 発展トピック「所有権の詳細とムーブセマンティクス」完了
- ✅ 所有権の3つのルール、ムーブ/コピーの違い、スタック/ヒープ、ダ
ブルフリー問題まで理解
めっちゃいい感じで進んでるよ!所有権の基本がしっかり理解できてる。
次はStage 2の「借用」だね。所有権を「貸す」仕組みで、実務で一番よ
く使うパターンだよ。楽しみにしててね!
また /dojo で続きから始められるから、好きなときにどうぞ!
じゃあね〜!👋
有用。