Struct mycelium_util::sync::cell::MutPtr

source ·
pub struct MutPtr<T: ?Sized>(/* private fields */);
Available on non-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.

MutPtrs 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, MutPtrs 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>

source

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.

source

pub fn with<F, R>(&self, f: F) -> R
where F: FnOnce(*mut T) -> 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`!

Trait Implementations§

source§

impl<T: Debug + ?Sized> Debug for MutPtr<T>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> Freeze for MutPtr<T>
where T: ?Sized,

§

impl<T> RefUnwindSafe for MutPtr<T>
where T: RefUnwindSafe + ?Sized,

§

impl<T> !Send for MutPtr<T>

§

impl<T> !Sync for MutPtr<T>

§

impl<T> Unpin for MutPtr<T>
where T: ?Sized,

§

impl<T> UnwindSafe for MutPtr<T>
where T: RefUnwindSafe + ?Sized,

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.