Struct mycelium_util::sync::cell::ConstPtr
source · pub struct ConstPtr<T: ?Sized>(/* private fields */);
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
.
ConstPtr
s are produced by the UnsafeCell::get
method. The pointed
value can be accessed using ConstPtr::deref
.
Any number of ConstPtr
s 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
, ConstPtr
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> ConstPtr<T>
impl<T: ?Sized> ConstPtr<T>
sourcepub unsafe fn deref(&self) -> &T
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.
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::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.