`UnsafeCell` and C code (or `ptr::read/write`)

Hey

Say you have code like

extern "C" {
    fn c_fn_which_mutates_data(*mut c_style_t);
}

CWrapper {
    c_ptr: *mut c_style_t,
}

impl CWrapper {
    fn do_thing(&self) {
        unsafe { c_fn_which_mutates_data(self.c_ptr) }
    }
}

does the *mut c_style_t need to be in an UnsafeCell? My intuition is "no", but I might be wrong.

Corollary

If the function was implemented in rust like

extern "C" fn c_fn_which_mutates_data(val: *mut c_style_t) {
    ptr::write(val, 42);
}

would you need an unsafe cell or not?

You would not need an UnsafeCell around the raw pointer itself, but depending on where you got the raw pointer from, you might need an UnsafeCell around what it points to.

1 Like

It would be a pointer returned from C code.

That should be fine.

1 Like

Is this still true if doing memory reads/writes in rust using ptr::read/write?

Yes, it is fine. How you read or write to the pointer is irrelevant. The important part is that if you got the pointer by converting a Rust reference into a pointer, special rules apply to that pointer.

3 Likes

UnsafeCell purpose is to interact exclusively with shared references (&T type vs. &UnsafeCell<T>). So all is fine, unless you create or "imply" the existence of such a shared reference:

  • &'_ (*mut T) does not "imply" &'_ T,

  • but any dereferenceable pointer such as &'_ mut T or Box<T> do:

    • &'_ (&'_ mut T) "implies" &'_ T

    • &'_ Box<T> "implies" &'_ T

1 Like

So you're saying that casting a &T to a *mut T will still cause UB if I violate the many immutable or 1 mutable ref contract (by e.g. calling ptr::write on the *mut T?

1 Like

Yes, a *mut T derived from &T still only allows immutable access.

2 Likes

Yes, it's always UB to write to the T behind a (live) &T, no matter how many pointer castings and things there are in the middle.

  • You can't ever write to a *mut T that was derived from a &T.
  • You can't write to a *mut T that was derived from a &mut T if there are also live &Ts that reference the same thing.
2 Likes

... unless your T is inside an UnsafeCell?

No, if you have a &T, that pointer can never be used to modify the T it points to. An &UnsafeCell<T> allows you to get an &mut T, sure, but if you are accessing the value through a &T instead of through the UnsafeCell, the usual rules apply

3 Likes

Sorry I meant what you are saying, that you have a &UnsafeCell<T>, then you can get mutable access to the T and the compiler can't assume that you won't.

Thanks for all the answers - it seems like the key takeaway is "it's not what the thing literally is that matters, it's what it semantically is" so if you have a &T then even though you can convert it to a *mut T you must not violate the contract to not mutate T, because it's still a &T to the compiler and so the compiler will assume T is immutable.

Yeah. The *mut T and *const T pointers are just lints that help make sure that you're doing the right thing. Besides sometimes requiring a cast due to these lints, the only difference is their variance.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.