自己参照型の作成方法について。
自己参照型では自身のフィールドが自身の他のフィールドを参照する。
コードがシンプルになる事がある。
例えば、親型と子型とがあり、子型から親型が参照される場合、それらをまとめた自己参照型が作れる。これがあると、親型と子型をまとめて作成して持ちまわれる。一方、これができないと、まず親型の値から作成して配置、次にそれを参照する子型の値を作成、そして両者それぞれを持ちまわる必要がある。
値の移動があると、内部の自己参照が壊れてしまう。
これは以下のようなメモリ配置を想像すると分かりやすい。
なお、値の移動は GC のない言語では日常である (GC があれば自由に参照を増やせるため、値の移動そのものが不要になる)。そのため、GC のない言語では自己参照型はあまり使われない。特に Rust では、参照が壊れるのを予防するために、アンセーフなしでは自己参照型を作れなくなっている。
自己参照型の作成には以下のアイテムの知識が必要になる。
Unpin
自動実装されるトレイト。
否自己参照型はこれを実装する。
PhantomPinned
Unpin を解除するマーカー。
自己参照型はこのフィールドを所有する。
Pin
ラップ対象の移動を制限するスマートポインタ。
対象が Unpin を持つ場合にのみ DerefMut が有効になる。
以下では ChildWithParent 型が自己参照型になっている。
use std::{marker::PhantomPinned, pin::Pin};
fn main() {
let cwp = child_with_parent();
assert_eq!(cwp.child().name, "ME");
assert_eq!(cwp.child().parent.name, "MAMA");
}
fn child_with_parent() -> Pin<Box<ChildWithParent>> {
let parent = Parent { name: "MAMA" };
ChildWithParent::new(parent, "ME")
}
struct Parent {
name: &'static str,
}
struct Child<'a> {
name: &'static str,
parent: &'a Parent,
}
struct ChildWithParent {
child: Option<Child<'static>>,
_parent: Parent,
_pp: PhantomPinned,
}
impl ChildWithParent {
pub fn new(parent: Parent, name: &'static str) -> Pin<Box<Self>> {
let mut ret = Box::pin(Self {
child: None,
_parent: parent,
_pp: PhantomPinned,
});
let parent_ref = unsafe { &*(&ret._parent as *const _) };
let child = Child { name, parent: parent_ref };
unsafe {
ret.as_mut().get_unchecked_mut().child = Some(child);
}
ret
}
pub fn child(&self) -> &Child<'_> {
self.child.as_ref().unwrap()
}
}