pub struct ConstPtr<T: ?Sized>(/* private fields */);
Available on non-loom only.
Expand description

An immutable raw pointer to an UnsafeCell that may be checked when Loom model checking is enabled.

This type is essentially a *const T, but with the added ability to participate in Loom’s UnsafeCell access tracking when the cfg(loom) cfg flag is set. While a ConstPtr to a given UnsafeCell exists, Loom will track that the UnsafeCell is being accessed immutably.

When cfg(loom) is not set, this type is equivalent to a normal *const T.

ConstPtrs are produced by the UnsafeCell::get method. The pointed value can be accessed using ConstPtr::deref.

Any number of ConstPtrs may concurrently access a given UnsafeCell. However, if the UnsafeCell is accessed mutably (by UnsafeCell::with_mut or UnsafeCell::get_mut) while a ConstPtr exists, Loom will detect the concurrent mutable and immutable accesses and panic.

Note that the cell is considered to be immutably accessed for the entire lifespan of the ConstPtr, not just when the ConstPtr is actively dereferenced.

Safety

Although the ConstPtr type is checked for concurrent access violations, it is still a raw pointer. A ConstPtr 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 ConstPtr does not dangle. However, unlike a normal *const T, ConstPtrs 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> ConstPtr<T>

source

pub unsafe fn deref(&self) -> &T

Dereference the raw pointer.

Safety

This is equivalent to dereferencing a *const T pointer, so all the same safety considerations apply here.

Because the ConstPtr 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(*const T) -> R,

Perform an operation with the actual value of the raw pointer.

This may be used to call functions like [ptr::read] and ptr::eq, which are not exposed by the ConstPtr 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 immutably” as long as the ConstPtr guard exists. When the guard is dropped, Loom considers the immutable access to have ended. This means that if the *const 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 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(); // tracked immutable access begins here

     // move the real pointer out of the simulated pointer
    tracked_ptr.with(|real_ptr| real_ptr)

}; // tracked immutable access *ends here* (when the tracked pointer is dropped)

// now, another thread can mutate the value *without* loom knowing it is being
// accessed concurrently by this thread! this is BAD NEWS ---  loom would have
// failed to detect a potential data race!
unsafe { println!("{}", (*ptr)) }

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>) -> *const usize {
    let tracked_ptr = cell.get(); // tracked immutable access begins here

    tracked_ptr.with(|ptr| unsafe {
        &(*ptr).bar as *const usize
    })
} // tracked access ends here, when `tracked_ptr` is dropped


// now, a pointer to `mystruct.bar` exists that Loom is not aware of!
// if another thread were to mutate `mystruct.bar` while we are holding this
// pointer, Loom would fail to detect the data race!
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.

Furthermore, 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();
let value_in_cell = ptr.with(|ptr| unsafe {
    // This is fine, because `ptr::read` does not retain ownership of
    // the pointer after when the function call returns.
    core::ptr::read(ptr)
});

But code like this is not okay:

use mycelium_util::sync::cell::UnsafeCell;
use core::ptr;

struct ListNode<T> {
   value: *const T,
   next: *const ListNode<T>,
}

impl<T> ListNode<T> {
    fn new(value: *const T) -> Box<Self> {
        Box::new(ListNode {
            value, // the pointer is moved into the `ListNode`, which will outlive this function!
            next: ptr::null::<ListNode<T>>(),
        })
    }
}

let cell = UnsafeCell::new(1);

let ptr = cell.get(); // immutable access begins here

// the pointer passed into `ListNode::new` will outlive the function call
let node = ptr.with(|ptr| ListNode::new(ptr));

drop(ptr); // immutable access ends here

// loom doesn't know that the cell can still be accessed via the `ListNode`!

Finally, the *const T passed to with should not be cast to an *mut T. This will permit untracked mutable access, as Loom only tracks the existence of a ConstPtr as representing an immutable access.

Trait Implementations§

source§

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

source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T: ?Sized> RefUnwindSafe for ConstPtr<T>
where T: RefUnwindSafe,

§

impl<T> !Send for ConstPtr<T>

§

impl<T> !Sync for ConstPtr<T>

§

impl<T: ?Sized> Unpin for ConstPtr<T>

§

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

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.

§

impl<T> Instrument for T

§

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

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

fn in_current_span(self) -> Instrumented<Self>

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

impl<T> Instrument for T

§

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

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

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.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more