rustを学んで⑦
Rustの学習記録:RcとRefCellで混乱した日
今日はRustの所有権システムの応用編、RcとRefCellについて勉強しました。正直かなり混乱しましたが、いくつか重要な気づきがあったので記録しておきます。
Rcの理解
Rc<T>(Reference Counted)は、ヒープ上のデータを複数の所有者で共有するための仕組みです。
BoxとRcの違い
- Box
: 所有者は1つだけ。ムーブすると元の変数は使えなくなる - Rc
: 複数の所有者が同じデータを指せる。参照カウント方式で管理
use std::rc::Rc;
let data = Rc::new(vec![1, 2, 3]);
let data2 = Rc::clone(&data); // 参照カウントが増えるだけ
let data3 = Rc::clone(&data);
println!("参照カウント: {}", Rc::strong_count(&data)); // 3
重要なのは、Rcだけでは中身を変更できないという点。変更したい場合はRc<RefCell<T>>と組み合わせる必要があります。
Rcの具体的な使用例
最初は「どんな時に使うの?」と疑問でしたが、いくつか納得できる例がありました。
1. 設定やリソースの共有
これが一番イメージしやすかったです。
use std::rc::Rc;
struct AppConfig {
database_url: String,
max_connections: usize,
}
let config = Rc::new(AppConfig {
database_url: "localhost:5432".to_string(),
max_connections: 100,
});
// 複数のモジュールで同じ設定を参照
let service1 = DatabaseService { config: Rc::clone(&config) };
let service2 = CacheService { config: Rc::clone(&config) };
データ的にも同じヒープから持ってくるのは効率的で、ベストプラクティスなんだろうなと思いました。
2. グラフやツリー構造
複数のノードから同じノードを参照する必要がある場合にも使われます。
struct Node {
value: i32,
children: Vec<Rc<Node>>, // 子ノードへの参照のリスト
}
混乱ポイント
Vec<Rc>の理解
最初はVec<Rc<Node>>が何なのか混乱しました。分解すると:
Vec<Rc<Node>>
│ │ └─ Node構造体
│ └──── Rcでラップされている
└──────── それらのベクタ(可変長配列)
つまり、子ノードへのポインタのリストということです。
Cons(i32, Rc)の再帰的構造
enum List {
Cons(i32, Rc<List>), // 値 と 次のリストへの参照
Nil, // リストの終わり
}
この再帰的なデータ構造は、正直まだよくわかりません。無理に今理解しようとする方が間違っている気がするので、基礎を固めてから戻ってくることにしました。
「Node」という言葉の多義性
「Node」という言葉が複数の意味で使われていて混乱しました:
Node(型) = 構造体の設計図Node { value: 10, ... }= その設計図から作られた実際のデータ(インスタンス)Rc::new(...)= そのデータをヒープに置いて、参照カウント付きポインタで包む
なのでRc<Node>は「Node型のインスタンスへの、共有可能なポインタ」という理解にたどり着きました。
RefCellについて
RefCellは「Rcの変更可能版」くらいの理解で今は押さえておくことにしました。
正確には:
Rc<T>= 複数箇所から共有できるが、変更不可Rc<RefCell<T>>= 複数箇所から共有でき、かつ変更も可能
use std::rc::Rc;
use std::cell::RefCell;
let data = Rc::new(RefCell::new(5));
let data2 = Rc::clone(&data);
*data.borrow_mut() += 1; // 変更できる
println!("{}", data2.borrow()); // 6
「共有したい + 変更もしたい = Rc<RefCell<T>>」と覚えておきます。
トレイト(trait)についての疑問
YouTubeでThe Bookの15.5を見ていて、新たな混乱が発生しました。
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.push(String::from(message));
}
}
講師が「このトレイトは〜」と説明するのですが、画面にはimplしか映っていない。「え?トレイトってtraitのことじゃないの?」と混乱しました。
トレイトとは
トレイトは**「このメソッドを持っていますよ」という約束・契約**です。他の言語でいうインターフェースに近いです。
// トレイトの定義(契約書)
trait Messenger {
fn send(&self, message: &str);
}
// トレイトの実装(インプル)
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
// 実装
}
}
整理:
trait= トレイトを定義するキーワードimpl Trait for Type= トレイトを**実装(インプル)**する構文
講師が「このトレイトは〜」と言うのは、「この(Messengerという)トレイトは〜」という省略した言い方だったようです。
用語について思ったこと
プログラミング用語は日本語に無理やり訳すより、カタカナや英語のままの方が圧倒的にわかりやすいです。
- impl (インプル) = implementation(実装)
- trait (トレイト) = 特性、特質 ← 日本語だと意味不明
- method (メソッド) = 方法、手法 ← これも微妙
Rustのドキュメントもコードも全部英語なので、カタカナや英語で覚えた方が絶対いいと思いました。
循環参照
The Bookの最後の方で循環参照について学びましたが、正直さっぱりでした。
RcとRefCellの組み合わせ- 再帰的なデータ構造
Weakポインタ
とか、難しい概念が一気に出てきて、めちゃめちゃ眠くなりました😴
今覚えておくこと:
- 「循環参照っていう問題がある」
- 「
Weakで解決できる」
くらいで十分だと判断。必要になったときに戻ってきます。
まとめ
今日の学び:
Rcは複数箇所から同じデータを参照したいときに使う- 設定の共有のような使い方が実用的でイメージしやすい
Rc<RefCell<T>>で共有 + 変更が可能になる- 再帰的データ構造は今は無理に理解しなくてOK
- 用語はカタカナ・英語で覚える方がいい
再帰的なデータ構造や循環参照は、基礎を固めてから改めて勉強することにします。焦らず、一歩ずつ進んでいきます!
次のステップ:Bashを学ぶ
ここまでRustを勉強してきて、正直お腹いっぱいになってきました。特にRc、RefCell、循環参照といった難しい概念が続いて、頭が飽和状態です。
そこで、次の一週間はThe Complete Bash Scripting CourseでBashについて学ぶことにしました。
なぜBashを学ぶのか?
-
頭の切り替え目的
Rustの難しい部分で詰まっているときに、全く違うことを学ぶのはリフレッシュになると信じている。 -
実用的で即効性の期待
Rustを書くときもターミナルを使うので、Bashの知識は直接役立ちそうだなと思った。ファイル操作、スクリプト作成など、すぐに使えるスキルをしっかりと学ぶ。 -
体系的なコース
7時間18分の完全なコースで、基礎から応用まで網羅されているみたい。YouTubeで無料で見られるのも魅力的。正し、英語なので気合い入れなきゃなと思っています。 -
Rustとの相乗効果の期待
Bashでスクリプトが書けると、Rustのビルドプロセスやテストの自動化に使えます。CLIツールを作るときの理解も深まるはずです。 -
一週間という区切り
「Rustを忘れる」というより「一旦休憩」という感じで、戻ったときに頭がクリアになっている可能性を期待する。
学習計画
次回からのブログは、Bashの学習記録を書いていきます。Rustの学習は中断するのではなく、一旦寝かせておいて、頭がリフレッシュしてから戻ってくる作戦です。
無理に詰め込むより、適度に休憩しながら、長期的に続けられる学習スタイルを目指します!