I'm trying to make a tree with parent pointers in Rust. A method on the node struct is giving me lifetime issues. Here's a minimal example, with lifetimes written explicitly so that I can understand them:
use core::mem::transmute;
pub struct LogNode<'n>(Option<&'n mut LogNode<'n>>);
impl<'n> LogNode<'n> {
pub fn child<'a>(self: &'a mut LogNode<'n>) -> LogNode<'a> {
LogNode(Some(self))
}
pub fn transmuted_child<'a>(self: &'a mut LogNode<'n>) -> LogNode<'a> {
unsafe {
LogNode(Some(
transmute::<&'a mut LogNode<'n>, &'a mut LogNode<'a>>(self)
))
}
}
}
(Playground link)
Rust complains about child
...
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter
'n
due to conflicting requirements
...but it's fine with transmuted_child
.
I think I understand why child
won't compile: the self
parameter's type is &'a mut LogNode<'n>
but the child node contains an &'a mut LogNode<'a>
, and Rust doesn't want to coerce LogNode<'n>
to LogNode<'a>
. If I change the mutable references to shared references, it compiles fine, so it sounds like the mutable references are a problem specifically because &mut T
is invariant over T
(whereas &T
is covariant). I guess the mutable reference in LogNode
bubbles up to make LogNode
itself invariant over its lifetime parameter.
But I don't understand why that's true—intuitively it feels like it's perfectly sound to take LogNode<'n>
and shorten its contents' lifetimes by turning it into a LogNode<'a>
. Since no lifetime is made longer, no value can be accessed past its lifetime, and I can't think of any other unsound behavior that could happen.
transmuted_child
avoids the lifetime issue because it sidesteps the borrow checker, but I don't know if the use of unsafe Rust is sound, and even if it is, I'd prefer to use safe Rust if possible. Can I?
I can think of three possible answers to this question:
child
can be implemented entirely in safe Rust, and here's how.child
cannot be implemented entirely in safe Rust, but transmuted_child
is sound.child
cannot be implemented entirely in safe Rust, and transmuted_child
is unsound.Edit 1: Fixed a claim that &mut T
is invariant over the lifetime of the reference. (Wasn't reading the nomicon right.)
Edit 2: Fixed my first edit summary.
The answer is #3: child
cannot be implemented in safe Rust, and transmuted_child
is unsound¹. Here's a program that uses transmuted_child
(and no other unsafe
code) to cause a segfault:
fn oops(arg: &mut LogNode<'static>) {
let mut short = LogNode(None);
let mut child = arg.transmuted_child();
if let Some(ref mut arg) = child.0 {
arg.0 = Some(&mut short);
}
}
fn main() {
let mut node = LogNode(None);
oops(&mut node);
println!("{:?}", node);
}
short
is a short-lived local variable, but since you can use transmuted_child
to shorten the lifetime parameter of the LogNode
, you can stuff a reference to short
inside a LogNode
that should be 'static
. When oops
returns, the reference is no longer valid, and trying to access it causes undefined behavior (segfaulting, for me).
¹ There is some subtlety to this. It is true that transmuted_child
itself does not have undefined behavior, but because it makes other code such as oops
possible, calling or exposing it may make your interface unsound. To expose this function as part of a safe API, you must take great care to not expose other functionality that would let a user write something like oops
. If you cannot do that, and you cannot avoid writing transmuted_child
, it should be made an unsafe fn
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With