Why is `DerefMut` not needed in this example?

The following code compiles without complaint, but I don't understand why! It is derived from a real-world use case where I am getting

trait DerefMut is required to modify through a dereference, but it is not implemented for PgRelation

Here, I don't get the error, even though afact I've set things up in a way that should trigger it. Hoping someone here can help!

use std::ops::Deref;

pub type PgStatCounter = i64;

#[repr(C)]
pub struct RelationData {
    pub pgstat_info: *mut PgStatTableStatus,
}

#[repr(C)]
pub struct PgStatTableCounts {
    pub numscans: PgStatCounter,
}

#[repr(C)]
pub struct PgStatTableStatus {
    pub counts: PgStatTableCounts,
}

pub struct PgRelation {
    boxed: Box<RelationData>,
}

impl Deref for PgRelation {
    type Target = Box<RelationData>;

    fn deref(&self) -> &Self::Target {
        &self.boxed
    }
}

fn main() {
    let mut status = PgStatTableStatus {
        counts: PgStatTableCounts {
            numscans: 0,   
        }
    };

    let rel = PgRelation {
        boxed: Box::new(
            RelationData {
                pgstat_info: &mut status as *mut PgStatTableStatus,
            }
        )
    };
    
    // Now try incrementing `numscans` via `rel`.  Will this work?
    unsafe {
        if !rel.pgstat_info.is_null() {
            // Since PgRelation does not implement DerefMut, I would expect an
            // error like:
            //
            //     error[E0596]: cannot borrow data in dereference of `PgRelation` as mutable
            //   --> src/lib.rs:13:11
            //    |
            // 13 |         (*rel.pgstat_info).counts.numscans += 1;
            //    |           ^^^ cannot borrow as mutable
            //    |
            //    = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `PgRelation`
            //
            // And yet, it compiles without errors!  Why?
            (*rel.pgstat_info).counts.numscans += 1;
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.57s
     Running `target/debug/playground`

You're using unsafe so you've told the compiler "don't check this, I know what I'm doing". There is no need for unsafe code here. Some options when you need to mutate something are:

  • call a method with &mut self -- limited to callers with exclusive access
  • use a Cell -- limited to callers in a single thread
  • use an AtomicI64 for the counter type and an Arc to share the struct among threads

Rule of thumb: don't use unsafe unless you've exhausted all other options and you know how to use it safely, which takes serious study.

1 Like

*mut T are copy, so the path of dereferences used to get to rel.pgstat_info doesn't matter in a sense. You don't have to keep the borrows of rel and the Box returned in the Deref implementations alive once you get to rel.pgstat_info -- the borrows can end at that point by creating a copy of the *mut and the rest of the expression can work off of that.

            let tmp = rel.pgstat_info;
            // Borrows no longer need to be alive here
            (*tmp).counts.numscans += 1;
1 Like

My code block where tmp isn't in a mut binding only works because deref on *mut _ is a builtin operation (i.e. not based on DerefMut and doesn't require a &mut tmp), so I decided to further check my instinct on this one. Given that playground, it could be that borrow checking does treat *mut _ special here, ala shared references as described in the NLL RFC.

Hmm. The use of unsafe is carried over from the original context where the code involves FFI calls. And dereferencing a raw pointer is also unsafe. All of the structs mocked up in the playground come from an external crate (pgrx) that I can't change, so your advice is not really actionable.

1 Like

Applying this refactoring back to the real-world context I was dealing with fixes my problem, so I'm unblocked, thanks!

Understood. Sometimes people post unsafe code because they don't know a safe way of doing it, and others because unsafe is required for reasons not stated. I guessed wrong.