Recently, I wrote a semaphore implementation in rust.
When writing the guard type, I settled on something that looks like this:
#[repr(transparent)]
pub struct Guard<T: Borrow<Semaphore>> {
semaphore: ManuallyDrop<T>,
}
impl<T: Borrow<Semaphore>> Drop for Guard<T> {
fn drop(&mut self) {
std::mem::drop(unsafe { self.destroy() });
}
}
impl<T: Borrow<Semaphore>> Guard<T> {
fn new(semaphore: T) -> Self {
Self {
semaphore: ManuallyDrop::new(semaphore),
}
}
/// Moves the semaphore out of `self` and releases it
// SAFETY: callers must ensure that `self` is never used again
unsafe fn destroy(&mut self) -> T {
let sem = unsafe { ManuallyDrop::take(&mut self.semaphore) };
sem.borrow().release_raw();
sem
}
/// Drops the guard, returning the underlying reference used to create it
pub fn release(mut self) -> T {
let sem = unsafe { self.destroy() };
std::mem::forget(self);
sem
}
}
pub trait SemaphoreExt: Borrow<Semaphore> + Sized {
/// Blocks until a value can be acquired from the semaphore. Returns a guard
/// that contains `Self`
fn acquire(self) -> Guard<Self>;
fn try_acquire(self) -> Result<Guard<Self>, Self>;
}
impl<T: Borrow<Semaphore>> SemaphoreExt for T {
fn acquire(self) -> Guard<Self> {
self.borrow().acquire_raw();
Guard::new(self)
}
fn try_acquire(self) -> Result<Guard<Self>, Self> {
if self.borrow().try_acquire_raw() {
Ok(Guard::new(self))
} else {
Err(self)
}
}
}
I was comparing it to std::sync::MutexGuard
, and would appreciate some other opinions about those differences.
The standard mutex guard seems to capture a &Mutex
. I like my approach because it allows for constructs like Guard<Arc<Semaphore>>
, which is 'static
. AFAIK, you can't create a std MutexGuard
with an owned lifetime. At the same time, Guard<&'_ Semaphore>
is a perfectly fine type, and behaves exactly as you'd expect.
I feel as though there must be limitations to my approach that I am not thinking of. Otherwise, this seems strictly more flexible than std::sync::MutexGuard
.