-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for mut ref dependents #60
Conversation
@steffahn do you mind giving this a review? |
This is less code, less run-time work and conceptually harder to miss-use. Win-win.
I wonder what the |
What I had in mind was that if the owner is not borrowed during construction, it could be borrowed later and then shoved into for example |
One point of comparison in terms of API is (code example meant for soundness demonstration; manual implementation with // Unstable, but 'MutBorrow::leak(…)' it could also be 'simulated' without this,
// via '&mut **Box::leak(Box::new(…))', at the cost of also leaking some memory
#![feature(cell_leak)]
use std::cell::{RefCell, RefMut};
pub struct MutBorrow<T>(RefCell<T>);
impl<T> MutBorrow<T> {
/// Constructs a new `MutBorrow`.
pub fn new(value: T) -> Self {
Self(RefCell::new(value))
}
/// Obtains a mutable reference to the underlying data.
///
/// This function can only sensibly be used in the builder function. Afterwards, it's impossible
/// to access the inner value, with the exception of [`MutBorrow::into_inner`].
///
/// # Panics
///
/// Will panic if called anywhere but in the dependent constructor. Will also panic if called
/// more than once.
pub fn borrow_mut(&self) -> &mut T {
let Ok(guard) = self.0.try_borrow_mut() else {
panic!("Tried to access locked MutBorrow")
};
RefMut::leak(guard)
}
/// Consumes `self` and returns the wrapped value.
pub fn into_inner(self) -> T {
self.0.into_inner()
}
} A downside of this overall approach, e.g. compared to ouroboros, would be that the usage of |
Actually, only …here’s how usage of
|
The comparison with
Good point, I think doing so would be a good fit for
I don't think that works. One could share a |
No, I do not believe wait, why did you not use Let me repeat the relevant section from my precious reply. pub fn borrow_mut(&self) -> &mut T {
// Ensure this function can only be called once.
// Relaxed should be fine, because only one thread could ever read `false` anyway,
// so further synchronization is pointless.
let was_locked = self.is_locked.swap(true, Relaxed);
if was_locked {
panic!("Tried to access locked MutBorrow")
} else {
// SAFETY: `self.is_locked` starts out as locked and can never be unlocked again, which
// guarantees that this function can only be called once. And the `self.value` being
// private ensures that there are no other references to it.
unsafe { &mut *self.value.get() }
}
} |
My bad, I had missed that part. You are right, disjointed load and store are non-sense here. Looking at the generated machine-code x86: mov al, 1
xchg byte ptr [rdi], al
test al, al
setne al
ret aarch64: str x30, [sp, #-16]!
mov x1, x0
mov w0, #1
bl __aarch64_swp1_relax / __aarch64_swp1_acq_rel
cmp w0, #0
cset w0, ne
ldr x30, [sp], #16
ret My mental model for |
With a more specific mov w8, #1
swpb / swpalb w8, w8, [x0]
cmp w8, #0
cset w0, ne
ret Based on this documentation https://www.scs.stanford.edu/~zyedidia/arm64/swpb.html
|
My mental model for memory orderings of atomic operations is that they are only about how the operations are ordered relative to other memory. E.g. if you have – mutex-style – one thread “unlocking” something (in an However, in this case we need no synchronization, because only one thread is ever going to touch the contained value anyways. Well, and at one point the value was constructed, and at the end it is destructed, but those accesses should already be accounted for by the mechanisms that would allow a As a different point of comparison; I believe that if you do a lot of The assembly is interesting; I had only checked x86 so far, good to know the difference can actually be meaningful on other platforms. Another operation here, instead of All that being said, realistically, this all seems completely irrelevant from a performance stand-point, so feel free to use any memory ordering you like. |
Regarding |
Oh, wow, a loop seems bad, why hadn’t I come across that yet? Ah, now I see what I did differently(!!): I had only compared Given that it looks like the compiler chooses to use the swap instruction/code to “improve” the |
9552c6d
to
9517eeb
Compare
Doing some further research I'm convinced you are right, only ever one thread could read I think it should be good to be merged now. |
This has feature that has been requested multiple times by users.
This also closes the last AFAIK feature gap to ouroboros.ouroboros supports async builders.Fixes #59 and #36.