Well, this is the problem. The very key of Rust is that no programmer should be able to write Undefined Behavior (unless they use unsafe
), even when they misuse libraries (e.g. your library). When that's the case, it is obviously okay to crash, to loop indefinitely or to give extraneous results; but only as long as there is no UB.
I still think that someone could build an high-level library built on top of QCell
s, and end up writing something along these lines:
use ::qcell::{
QCell,
QCellOwner,
};
fn entangle<T> (value: T) -> (QCellOwner, QCell<T>)
{
let owner = QCellOwner::
new()
;
let value = QCell::new(&owner, value);
(owner, value)
}
It might dumb and inefficient and not the way you had intended the lib to be used (I think that there could definitely be use cases for the entangle
function), but since you have not marked any of the API functions as unsafe
, users are allowed to write this code; any undefined behavior would not be their responsibility but yours (that's what unsafe fn
vs fn
means).
And obviously once a function such as entangle
becomes popular it is only a matter of time until someone writes code equivalent to the following sketchy run
function:
fn run (count: usize)
{
let (mut first_owner, value_1) = entangle(42);
(0 .. count)
.for_each(|_| { entangle(1); })
;
let (mut second_owner, value_2) = entangle(27);
dbg!(first_owner.get(&value_1));
dbg!(second_owner.get(&value_2));
swap(
first_owner.get_mut(&value_1),
second_owner.get_mut(&value_1), // wops, wrong copy_paste
);
}
fn swap (x: &mut usize, y: &mut usize)
{
let (old_x, old_y) = (*x, *y);
*x ^= *y;
*y ^= *x;
*x ^= *y;
assert!(
*x == old_y && *y == old_x,
"Only UNDEFINED BEHAVIOR could break this assertion",
);
}
There is obviously an error in the code, here, since accessing a variable with two different owners is just plain dumb and absurd. But since the users have not written any unsafe
code, Rust guarantees that all this will do is either panic!
, loop
indefinitely, or give extraneous results.
Given your get_mut
implementation with the id
check, the code does panic
99.99999%
of the time, and that's great ... Except for that not being 100%
. If count == core::u32::MAX
, then the code is UB!
- Playground (it uses
u16
instead of u32
for faster demo purposes)
Now, if you feel like you wouldn't like the overflow check, then you can add an unsafe fn
new_unchecked
constructor for QCellOwner
, so that the responsibility of misuse falls onto the user instead of you.
That is a very good attitude and I strongly encourage that 
Now, your program's invariant is based on the unicity of the owners' id
s, which can indeed be ensured with a (strictly) monotonic function. But when we think about it, all we need is a strictly injective function; for instance a function that randomly sampled integers and checked their not having been already sampled using a memoized set would be a valid (although stupidly slow) implementation. Monotonic counters are used for injectivity since they are the simplest implementation, but the ordering is actually not important in your case.
-
I never said not to use AtomicUsize
, I suggested you give users a choice by having two distinc QCellOwner-QCell
pairs:
-
by the way your QCell
is not Sync
, which means it can still not be used with an Arc
to be sent across threads, you should fix that:
-
unsafe impl<T : Send + Sync> Sync for AQCell<T> {}
-
unsafe impl<T : Send> Send for AQCell<T> {}
(in case you plan on adding an .into_inner()
method for {A,}QCell
)