Rustのスマートポインタの深掘り
Olivia Novak
Dev Intern · Leapcell

Rustのスマートポインタの深掘り
スマートポインタとは何ですか?
スマートポインタは、データを所有するだけでなく、追加の機能も提供するデータ構造の一種です。これらはポインタの高度な形式です。
ポインタとは、メモリアドレスを含む変数の一般的な概念です。このアドレスは、別のデータを「指す」または参照します。Rustの参照は&
記号で示され、指し示す値を借用します。参照は、追加機能を提供せずにデータへのアクセスのみを許可します。また、追加のオーバーヘッドがないため、Rustで広く使用されています。
Rustのスマートポインタは、特別な種類のデータ構造です。単にデータを借用する通常のポインタとは異なり、スマートポインタは通常、データを所有します。また、追加の機能も提供します。
Rustではスマートポインタは何に使用され、どのような問題を解決しますか?
スマートポインタは、プログラマーがメモリと並行性をより安全かつ効率的に管理するのに役立つ強力な抽象化を提供します。これらの抽象化には、スマートポインタや内部可変性を提供する型が含まれます。例:
Box<T>
は、ヒープ上に値を割り当てるために使用されます。Rc<T>
は、データの複数所有を可能にする参照カウント型です。RefCell<T>
は内部可変性を提供し、同じデータへの複数の可変参照を可能にします。
これらの型は標準ライブラリで定義されており、柔軟なメモリ管理を提供します。スマートポインタの重要な特性は、Drop
およびDeref
トレイトを実装していることです。
Drop
トレイトは、スマートポインタがスコープ外になると呼び出されるdrop
メソッドを提供します。Deref
トレイトは、自動デリファレンスを可能にします。つまり、ほとんどの場合、スマートポインタを手動でデリファレンスする必要はありません。
Rustの一般的なスマートポインタ
Box<T>
Box<T>
は最も単純なスマートポインタです。ヒープに値を割り当て、スコープ外になると自動的にメモリを解放します。
Box<T>
の一般的な使用例は次のとおりです。
- 再帰型など、コンパイル時にサイズが不明な型のメモリを割り当てる場合。
- スタックに保存したくない大きなデータ構造を管理し、スタックオーバーフローを回避する場合。
- 型のみが気になる値を所有する場合。たとえば、クロージャを関数に渡す場合などです。
簡単な例を次に示します。
fn main() { let b = Box::new(5); println!("b = {}", b); }
この例では、変数b
はヒープ上の値5
を指すBox
を保持しています。プログラムはb = 5
を出力します。ボックス内のデータは、スタックに格納されているかのようにアクセスできます。b
がスコープ外になると、Rustはスタックに割り当てられたボックスとヒープに割り当てられたデータの両方を自動的に解放します。
ただし、Box<T>
は複数の所有者によって同時に参照できません。例:
enum List { Cons(i32, Box<List>), Nil, } use List::{Cons, Nil}; fn main() { let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); let b = Cons(3, Box::new(a)); let c = Cons(4, Box::new(a)); }
このコードは、error[E0382]: use of moved value: a
というエラーになります。これは、a
の所有権がすでに移動されているためです。複数所有を有効にするには、Rc<T>
が必要です。
Rc<T> - 参照カウント
Rc<T>
は、データの複数所有を可能にする参照カウントスマートポインタです。最後の所有者がスコープ外になると、データは自動的に割り当て解除されます。ただし、Rc<T>
はスレッドセーフではありません。マルチスレッド環境では使用できません。
Rc<T>
の一般的な使用例は次のとおりです。
- プログラムの複数の部分でデータを共有し、
Box<T>
で発生した所有権の問題を解決する場合。 - メモリリークを回避するために、
Weak<T>
とともに循環参照を作成する場合。
データを共有するためにRc<T>
を使用する方法を示す例を次に示します。
use std::rc::Rc; fn main() { let data = Rc::new(vec![1, 2, 3]); let data1 = data.clone(); let data2 = data.clone(); println!("data: {:?}", data); println!("data1: {:?}", data1); println!("data2: {:?}", data2); }
この例では:
Rc::new
は、Rc<T>
の新しいインスタンスを作成するために使用されます。clone
メソッドは、参照カウントをインクリメントし、同じ値への新しいポインタを作成するために使用されます。- 最後の
Rc
ポインタがスコープ外になると、値は自動的に割り当て解除されます。
ただし、Rc<T>
は複数のスレッドでの同時使用には安全ではありません。これに対処するために、RustはArc<T>
を提供します。
Arc<T> - アトミックに参照カウント
Arc<T>
は、Rc<T>
のスレッドセーフなバリアントです。複数のスレッドが同じデータの所有権を共有できます。最後の参照がスコープ外になると、データは割り当て解除されます。
Arc<T>
の一般的な使用例は次のとおりです。
- 複数のスレッドでデータを安全に共有する場合。
- スレッド間でデータを転送する場合。
スレッド間でデータを共有するためにArc<T>
を使用する方法を示す例を次に示します。
use std::sync::Arc; use std::thread; fn main() { let data = Arc::new(vec![1, 2, 3]); let data1 = Arc::clone(&data); let data2 = Arc::clone(&data); let handle1 = thread::spawn(move || { println!("data1: {:?}", data1); }); let handle2 = thread::spawn(move || { println!("data2: {:?}", data2); }); handle1.join().unwrap(); handle2.join().unwrap(); }
この例では:
Arc::new
は、スレッドセーフな参照カウントポインタを作成します。Arc::clone
は、複数のスレッドに対して参照カウントを安全にインクリメントするために使用されます。- 各スレッドは
Arc
の独自のクローンを取得し、すべての参照がスコープ外になると、データは割り当て解除されます。
Weak<T> - 弱参照型
Weak<T>
は、Rc<T>
またはArc<T>
とともに使用して循環参照を作成できる弱参照型です。Rc<T>
およびArc<T>
とは異なり、Weak<T>
は参照カウントを増やしません。つまり、データがドロップされるのを防ぎません。
Weak<T>
の一般的な使用例は次のとおりです。
- ライフサイクルに影響を与えることなく値を監視する場合。
- メモリリークを回避するために、強力な参照サイクルを中断する場合。
Rc<T>
とWeak<T>
を使用して循環参照を作成する方法を示す例を次に示します。
use std::rc::{Rc, Weak}; struct Node { value: i32, next: Option<Rc<Node>>, prev: Option<Weak<Node>>, } fn main() { let first = Rc::new(Node { value: 1, next: None, prev: None }); let second = Rc::new(Node { value: 2, next: None, prev: Some(Rc::downgrade(&first)) }); first.next = Some(second.clone()); }
この例では:
Rc::downgrade
は、Weak
参照を作成するために使用されます。prev
フィールドはWeak
参照を保持しており、参照カウントに寄与せず、メモリリークを防ぎます。Weak
参照にアクセスするときは、.upgrade()
を呼び出して、Rc
に変換しようとすることができます。値が割り当て解除されている場合、upgrade
はNone
を返します。
UnsafeCell<T>
UnsafeCell<T>
は、不変の参照を通じてデータを変更できる低レベルの型です。Cell<T>
およびRefCell<T>
とは異なり、UnsafeCell<T>
はランタイムチェックを実行しません。これにより、他の内部可変性型を構築するための基礎となります。
UnsafeCell<T>
に関する重要なポイント:
- 誤って使用すると、未定義の動作につながる可能性があります。
- 通常、低レベルのパフォーマンスが重要なコード、または内部可変性を必要とするカスタム型を実装する場合に使用されます。
UnsafeCell<T>
の使用例を次に示します。
use std::cell::UnsafeCell; fn main() { let x = UnsafeCell::new(1); let y = &x; let z = &x; unsafe { *x.get() = 2; *y.get() = 3; *z.get() = 4; } println!("x: {}", unsafe { *x.get() }); }
この例では:
UnsafeCell::new
は、新しいUnsafeCell
を作成します。.get()
メソッドは、内部のデータを変更できる生のポインタを提供します。- 変更は
unsafe
ブロック内で行われます。Rustはメモリの安全性を保証できないためです。
注:UnsafeCell<T>
はRustの安全性の保証をバイパスするため、注意して使用する必要があります。ほとんどの場合、安全な内部可変性にはCell<T>
またはRefCell<T>
を使用することをお勧めします。
Cell<T>
Cell<T>
は、Rustで内部可変性を可能にする型です。不変の参照を持っている場合でも、データを変更できます。ただし、Cell<T>
は、値をコピーして出し入れすることで内部可変性を実現するため、Copy
トレイトを実装する型でのみ機能します。
Cell<T>
の一般的な使用例:
- 不変の参照を通じてデータを変更する必要がある場合。
- 可変フィールドが必要な構造体があるが、構造体自体は可変でない場合。
Cell<T>
の使用例:
use std::cell::Cell; fn main() { let x = Cell::new(1); let y = &x; let z = &x; x.set(2); y.set(3); z.set(4); println!("x: {}", x.get()); }
この例では:
Cell::new
は、値1
を含む新しいCell<T>
インスタンスを作成します。set
は、参照y
およびz
が不変であっても、内部値を変更するために使用されます。get
は、値を取得するために使用されます。
Cell<T>
はコピーセマンティクスを使用するため、Copy
トレイトを実装する型でのみ機能します。Copy
型でない型(Vec
やカスタム構造体など)の内部可変性が必要な場合は、RefCell<T>
の使用を検討してください。
RefCell<T>
RefCell<T>
は、内部可変性を可能にする別の型ですが、Copy
型でない型に対して機能します。Cell<T>
とは異なり、RefCell<T>
はRustの借用ルールをコンパイル時ではなくランタイムに適用します。
- 複数の不変借用または1つの可変借用を許可します。
- 借用ルールに違反すると、
RefCell<T>
はランタイムにパニックになります。
RefCell<T>
の一般的な使用例:
- 不変の参照を通じて
Copy
型でない型を変更する必要がある場合。 - それ以外の場合は不変である必要がある構造体内に可変フィールドが必要な場合。
RefCell<T>
の使用例:
use std::cell::RefCell; fn main() { let x = RefCell::new(vec![1, 2, 3]); let y = &x; let z = &x; x.borrow_mut().push(4); y.borrow_mut().push(5); z.borrow_mut().push(6); println!("x: {:?}", x.borrow()); }
この例では:
RefCell::new
は、ベクターを含む新しいRefCell<T>
を作成します。borrow_mut()
は、可変参照をデータに取得するために使用され、不変参照を介して突然変異を起こすことができます。borrow()
は、読み取りのために不変参照を取得するために使用されます。
重要な注意点:
-
ランタイム借用チェック: 通常、Rustの借用ルールはコンパイル時に適用されますが、
RefCell<T>
はこれらのチェックをランタイムに延期します。不変の借用がまだアクティブな間に可変に借用しようとすると、プログラムはパニックになります。 -
借用競合の回避: たとえば、次のコードはランタイムにパニックになります。
let x = RefCell::new(5); let y = x.borrow(); let z = x.borrow_mut(); // `y`がまだアクティブな不変の借用であるため、これはパニックになります。
したがって、RefCell<T>
は柔軟ですが、借用競合を回避するように注意する必要があります。
主要なスマートポインタのまとめ
スマートポインタ | スレッドセーフ | 複数オーナーを許可 | 内部可変性 | ランタイム借用チェック |
---|---|---|---|---|
Box<T> | ❌ | ❌ | ❌ | ❌ |
Rc<T> | ❌ | ✅ | ❌ | ❌ |
Arc<T> | ✅ | ✅ | ❌ | ❌ |
Weak<T> | ✅ | ✅ (弱い所有権) | ❌ | ❌ |
Cell<T> | ❌ | ❌ | ✅ (コピー型のみ) | ❌ |
RefCell<T> | ❌ | ❌ | ✅ | ✅ |
UnsafeCell<T> | ✅ | ❌ | ✅ | ❌ |
適切なスマートポインタを選択する
- 単一の所有権でヒープ割り当てが必要な場合は、
Box<T>
を使用します。 - シングルスレッドコンテキストで複数所有が必要な場合は、
Rc<T>
を使用します。 - 複数のスレッド間で複数所有が必要な場合は、
Arc<T>
を使用します。 Rc<T>
またはArc<T>
を使用して参照サイクルを防ぐには、Weak<T>
を使用します。- 内部可変性が必要な
Copy
型には、Cell<T>
を使用します。 - 内部可変性が必要な
Copy
型でない型には、RefCell<T>
を使用します。 - 手動の安全チェックが許容される、低レベルでパフォーマンスが重要なシナリオでのみ
UnsafeCell<T>
を使用します。
Leapcellは、Rustプロジェクトをホストするための最適な選択肢です。
Leapcellは、Webホスティング、非同期タスク、およびRedisのための次世代サーバーレスプラットフォームです。
多言語サポート
- Node.js、Python、Go、またはRustで開発します。
無制限のプロジェクトを無料でデプロイ
- 使用量に対してのみ料金を支払います - リクエストも料金もありません。
比類のないコスト効率
- アイドル料金なしの従量課金制。
- 例:25ドルで、平均応答時間60msで694万リクエストをサポートします。
合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI / CDパイプラインとGitOps統合。
- 実用的な洞察のためのリアルタイムメトリックとロギング。
簡単なスケーラビリティと高いパフォーマンス
- 高い同時実行性を簡単に処理するための自動スケーリング。
- 運用上のオーバーヘッドをゼロに - 構築に集中してください。
ドキュメントで詳細をご覧ください!
Xでフォローしてください:@LeapcellHQ