Unsafe {} blocks change borrowck semantics in subtle ways

I have a struct which wraps a raw pointer and I'm trying to implement std::ops::Deref for it.

#[repr(C)]
pub struct Archive_ref {
    pub pointer: *const ArchiveOpaqueType
}

What I have discovered is that depending on where the unsafe block is, the code either passes or fails borrowck.

This impl gives an error: borrowed value does not live long enough. Rust Playground

impl ::std::ops::Deref for Archive_ref {
    type Target = ActualArchiveType;
    fn deref(&self) -> &Self::Target {
         &(unsafe { *self.pointer }).0
    }
}

This impl works fine: Rust Playground

impl ::std::ops::Deref for Archive_ref {
    type Target = ActualArchiveType;
    fn deref(&self) -> &Self::Target {
         unsafe { &(*self.pointer).0 }
    }
}

Why do unsafe blocks affect this?

Edit: a third example shows that a normal {} block breaks the impl, just like the unsafe {} block: Rust Playground

It's not due to unsafe. It's borrow vs move.

Observe that

  • &foo.0 borrows from foo, while
  • &{foo}.0 first moves foo and then borrows the moved value, which is a temporary.

See this playground link for an example.

1 Like

Actually, I should add that you can still do it your way, minimizing the scope of the unsafe by moving a reference to the data within the unsafe block:

&(unsafe { &*self.pointer }).0

Here, you are moving a borrowed reference and .0 auto-derefs that reference.
See the second example in this playground link.

1 Like

Ah okay that's a very helpful explanation thank you... Do you think this is perhaps worth looking at as part of the learning curve discussion going on at the moment? ([Roadmap 2017] Productivity: learning curve and expressiveness - Rust Internals)

Like, I understand why this happens after being given an explanation, but... It feels as if there could be a way to make this simpler (maybe just an improvement of the error message for this particular case - no change of language semantics involved)