Struct mycelium_util::sync::cell::MutPtr
source · pub struct MutPtr<T: ?Sized>(/* private fields */);
loom
only.Expand description
A mutable raw pointer to an UnsafeCell
that may be checked when
Loom model checking is enabled.
This type is essentially a *mut T
, but with the added ability to
participate in Loom’s UnsafeCell
access tracking when the
cfg(loom)
cfg flag is set. While a MutPtr
to a given UnsafeCell
exists, Loom will track that the UnsafeCell
is
being accessed mutably.
When cfg(loom)
is not set, this type is equivalent to a normal
*mut T
.
MutPtr
s are produced by the UnsafeCell::get_mut
method. The pointed
value can be accessed using MutPtr::deref
.
If an UnsafeCell
is accessed mutably (by UnsafeCell::with_mut
or
UnsafeCell::get_mut
) or immutably (by UnsafeCell::with
or
UnsafeCell::get
) while a MutPtr
to that cell exists, Loom will
detect the invalid accesses and panic.
Note that the cell is considered to be mutably accessed for the entire
lifespan of the MutPtr
, not just when the MutPtr
is actively
dereferenced.
§Safety
Although the MutPtr
type is checked for concurrent access violations, it
is still a raw pointer. A MutPtr
is not bound to the lifetime of the
UnsafeCell
from which it was produced, and may outlive the cell. Loom
does not currently check for dangling pointers. Therefore, the user is
responsible for ensuring that a MutPtr
does not dangle. However, unlike
a normal *mut T
, MutPtr
s may only be produced from a valid
UnsafeCell
, and therefore can be assumed to never be null.
Additionally, it is possible to write code in which raw pointers to an
UnsafeCell
are constructed that are not checked by Loom. If a raw
pointer “escapes” Loom’s tracking, invalid accesses may not be detected,
resulting in tests passing when they should have failed. See here for
details on how to avoid accidentally escaping the model.
Implementations§
source§impl<T: ?Sized> MutPtr<T>
impl<T: ?Sized> MutPtr<T>
sourcepub unsafe fn deref(&self) -> &mut T
pub unsafe fn deref(&self) -> &mut T
Dereference the raw pointer.
§Safety
This is equivalent to dereferencing a *mut T
pointer, so all the same
safety considerations apply here.
Because the MutPtr
type can only be created by calling
UnsafeCell::get_mut
on a valid UnsafeCell
, we know the pointer
will never be null.
Loom tracks whether the value contained in the UnsafeCell
from which
this pointer originated is being concurrently accessed, and will panic
if a data race could occur. However, loom
does not track liveness
— the UnsafeCell
this pointer points to may have been dropped.
Therefore, the caller is responsible for ensuring this pointer is not
dangling.
sourcepub fn with<F, R>(&self, f: F) -> R
pub fn with<F, R>(&self, f: F) -> R
Perform an operation with the actual value of the raw pointer.
This may be used to call functions like ptr::write
, [ptr::read]
,
and ptr::eq
, which are not exposed by the MutPtr
type, cast the
pointer to an integer, et cetera.
§Correct Usage
Note that the raw pointer passed into the closure must not be moved out of the closure, as doing so will allow it to “escape” Loom’s ability to track accesses.
Loom considers the UnsafeCell
from which this pointer originated to
be “accessed mutably” as long as the MutPtr
guard exists. When the
guard is dropped, Loom considers the mutable access to have ended. This
means that if the *mut T
passed to a with
closure is moved out of
that closure, it may outlive the guard, and thus exist past the end of
the mutable access (as understood by Loom).
For example, code like this is incorrect:
use mycelium_util::sync::cell::UnsafeCell;
let cell = UnsafeCell::new(1);
let ptr = {
let tracked_ptr = cell.get_mut(); // tracked mutable access begins here
// move the real pointer out of the simulated pointer
tracked_ptr.with(|real_ptr| real_ptr)
}; // tracked mutable access *ends here* (when the tracked pointer is dropped)
// now, we can mutate the value *without* loom knowing it is being mutably
// accessed! this is BAD NEWS --- if the cell was being accessed concurrently,
// loom would have failed to detect the error!
unsafe { (*ptr) = 2 }
More subtly, if a new pointer is constructed from the original pointer, that pointer is not tracked by Loom, either. This might occur when constructing a pointer to a struct field or array index. For example, this is incorrect:
use mycelium_util::sync::cell::UnsafeCell;
struct MyStruct {
foo: usize,
bar: usize,
}
let my_struct = UnsafeCell::new(MyStruct { foo: 1, bar: 1});
fn get_bar(cell: &UnsafeCell<MyStruct>) -> *mut usize {
let tracked_ptr = cell.get_mut(); // tracked mutable access begins here
tracked_ptr.with(|ptr| unsafe {
&mut (*ptr).bar as *mut usize
})
} // tracked mutable access ends here, when `tracked_ptr` is dropped
// now, a pointer to `mystruct.bar` exists that Loom is not aware of!
// if we were to mutate `mystruct.bar` through this pointer while another
// thread was accessing `mystruct` concurrently, Loom would fail to detect
/// this.
let ptr_to_bar = get_bar(&my_struct);
Similarly, constructing pointers via pointer math (such as offset
)
may also escape Loom’s ability to track accesses.
Finally, the raw pointer passed to the with
closure may only be passed
into function calls that don’t take ownership of that pointer past the
end of the function call. Therefore, code like this is okay:
use mycelium_util::sync::cell::UnsafeCell;
let cell = UnsafeCell::new(1);
let ptr = cell.get_mut();
let value_in_cell = ptr.with(|ptr| unsafe {
// This is fine, because `ptr::write` does not retain ownership of
// the pointer after when the function call returns.
core::ptr::write(ptr, 2)
});
But code like this is not okay:
use mycelium_util::sync::cell::UnsafeCell;
use core::sync::atomic::{AtomicPtr, Ordering};
static SOME_IMPORTANT_POINTER: AtomicPtr<usize> = AtomicPtr::new(core::ptr::null_mut());
fn mess_with_important_pointer(cell: &UnsafeCell<usize>) {
cell.get_mut() // mutable access begins here
.with(|ptr| {
SOME_IMPORTANT_POINTER.store(ptr, Ordering::SeqCst);
})
} // mutable access ends here
// loom doesn't know that the cell can still be accessed via the `AtomicPtr`!