I'm creating a bidirectional graph in rust and the parent pointer are an obvious problem with the rust borrowing rules so I used raw pointers which are in theory safe since the parent owns the child and cannot be dropped before the child. The only problem I have (in my head) is when the graph moves. If the graph moves all parent pointer should be invalid as they point into the old location of the graph.
But testing by example showed me different results. Not only where the old pointer still valid the debugger also showed a new entry in "Register>Effective Address" which mapped old pointer to new location. My test code:
fn main() -> Result<()> {
let m = test();
println!("MT:{}, IT:{}, PTR_MT:{}", m.j, m.inner.i, unsafe {(*m.inner.parent).j});
Ok(())
}
fn test() -> Box<MyType> {
let mut mytype = MyType { inner: MyInnerType { parent: std::ptr::null_mut(), i: 5 }, j:69 };
println!("MT:{}, IT:{}", mytype.j, mytype.inner.i);
mytype.inner.parent = &mut mytype;
Box::new(mytype)
}
struct MyType {
inner: MyInnerType,
j:i32
}
struct MyInnerType {
parent: *mut MyType,
i:i32
}
Is this behavior defined and can I rely on it working?
No, this is not safe. It is unsound and exhibits undefined behavior. "The program works as intended" is one possible outcome, but it is not the only one.
Box::new(mytype)
movesmytype
into a heap allocation, which is not UB. However, evaluating*m.inner.parent
later causes UB as the pointer target has been moved.You should generally avoid raw pointers in Rust unless you are either implementing a safe abstraction on top of them or you are using FFI and cannot accomplish what you need to without them.