Skip to main content

ffi_cell/
lib.rs

1use std::{
2  fmt::Display,
3  marker::PhantomData,
4  ops::{Deref, DerefMut},
5  ptr::{NonNull, null_mut},
6  sync::atomic::{AtomicBool, AtomicPtr, Ordering},
7};
8
9use derive_more::{Display, Error, From};
10
11#[cfg(test)]
12mod test;
13
14pub struct FfiCell<T: Sync> {
15  ptr: AtomicPtr<T>,
16  in_use: AtomicBool,
17}
18
19impl<T: Sync> FfiCell<T> {
20  pub const fn new() -> Self {
21    Self {
22      ptr: AtomicPtr::new(null_mut()),
23      in_use: AtomicBool::new(false),
24    }
25  }
26
27  #[track_caller]
28  pub fn run<R>(&self, object: &mut T, f: impl FnOnce() -> R) -> R {
29    self.try_run(object, f).unwrap_or_display_err()
30  }
31
32  pub fn try_run<R>(
33    &self,
34    object: &mut T,
35    f: impl FnOnce() -> R,
36  ) -> Result<R, Error> {
37    unsafe {
38      self.try_lend(object)?;
39    }
40    let _reclaim = ScopeGuard::new(|| self.reclaim());
41    Ok(f())
42  }
43
44  /// # Safety
45  /// The object pointed to in the params cannot be referenced until
46  /// `reclaim` is called without panicking or `try_reclaim` is called and
47  /// returns `Ok`.
48  #[track_caller]
49  pub unsafe fn lend(&self, ptr: &mut T) {
50    unsafe { self.try_lend(ptr).unwrap_or_display_err() }
51  }
52
53  /// # Safety
54  /// The object pointed to in the params cannot be referenced until
55  /// `reclaim` is called without panicking or `try_reclaim` is called and
56  /// returns `Ok`.
57  pub unsafe fn try_lend(&self, ptr: &mut T) -> Result<(), LendError> {
58    // This check does not satisfy the safety requirement.
59    // It is here to provide a better error message.
60    if self.in_use.load(Ordering::SeqCst) {
61      return Err(LendError::AlreadyLent);
62    }
63
64    match self.ptr.compare_exchange(
65      null_mut(),
66      ptr,
67      Ordering::SeqCst,
68      Ordering::SeqCst,
69    ) {
70      Ok(_) => Ok(()),
71      Err(_) => Err(LendError::AlreadyHasLoan),
72    }
73  }
74
75  #[track_caller]
76  pub fn borrow(&self) -> impl DerefMut<Target = T> {
77    self.try_borrow().unwrap_or_display_err()
78  }
79
80  pub fn try_borrow(&self) -> Result<impl DerefMut<Target = T>, BorrowError> {
81    if self.in_use.swap(true, Ordering::SeqCst) {
82      Err(BorrowError::AlreadyBorrowed)
83    } else {
84      let ptr = self.ptr.swap(null_mut(), Ordering::SeqCst);
85      match NonNull::new(ptr) {
86        Some(ptr) => Ok(FfiGuard {
87          ptr,
88          cell: self,
89          _marker: PhantomData,
90        }),
91        None => Err(BorrowError::Unavailable),
92      }
93    }
94  }
95
96  #[track_caller]
97  pub fn reclaim(&self) {
98    self.try_reclaim().unwrap_or_display_err()
99  }
100
101  pub fn try_reclaim(&self) -> Result<(), ReclaimError> {
102    if self.in_use.load(Ordering::SeqCst) {
103      Err(ReclaimError::InUse)
104    } else if self.ptr.swap(null_mut(), Ordering::SeqCst).is_null() {
105      unreachable!("missing pointer when not in use")
106    } else {
107      Ok(())
108    }
109  }
110}
111
112impl<T: Sync> Default for FfiCell<T> {
113  fn default() -> Self {
114    Self::new()
115  }
116}
117
118struct FfiGuard<'g, T: Sync> {
119  ptr: NonNull<T>,
120  cell: &'g FfiCell<T>,
121  _marker: PhantomData<&'g ()>,
122}
123
124impl<'g, T: Sync> Deref for FfiGuard<'g, T> {
125  type Target = T;
126
127  fn deref(&self) -> &Self::Target {
128    unsafe { self.ptr.as_ref() }
129  }
130}
131
132impl<'g, T: Sync> DerefMut for FfiGuard<'g, T> {
133  fn deref_mut(&mut self) -> &mut Self::Target {
134    unsafe { self.ptr.as_mut() }
135  }
136}
137
138impl<'g, T: Sync> Drop for FfiGuard<'g, T> {
139  fn drop(&mut self) {
140    self
141      .cell
142      .ptr
143      .compare_exchange(
144        null_mut(),
145        self.ptr.as_ptr(),
146        Ordering::SeqCst,
147        Ordering::SeqCst,
148      )
149      .expect("tried to return lent pointer, but another pointer was there");
150    let was_in_use = self.cell.in_use.swap(false, Ordering::SeqCst);
151    assert!(was_in_use, "object was not in use when it was returned");
152  }
153}
154
155#[non_exhaustive]
156#[derive(Debug, Display, Error, From)]
157pub enum Error {
158  LendError(LendError),
159  BorrowError(BorrowError),
160}
161
162#[non_exhaustive]
163#[derive(Debug, Display, Error)]
164#[display("cannot lend value to ffi-cell because {_variant}")]
165pub enum LendError {
166  #[display("it currently has one and it is already lent out")]
167  AlreadyLent,
168  #[display("it already has one")]
169  AlreadyHasLoan,
170}
171
172#[non_exhaustive]
173#[derive(Debug, Display, Error)]
174#[display("cannot borrow value from ffi-cell because {_variant}")]
175pub enum BorrowError {
176  #[display("the cell does not have a value")]
177  Unavailable,
178  #[display("the cell's value is already lent out")]
179  AlreadyBorrowed,
180}
181
182#[non_exhaustive]
183#[derive(Debug, Display, Error)]
184#[display("cannot reclaim value from ffi-cell because {_variant}")]
185pub enum ReclaimError {
186  #[display("it is currently in use")]
187  InUse,
188}
189
190struct ScopeGuard<F: FnMut()>(F);
191
192impl<F: FnMut()> ScopeGuard<F> {
193  fn new(f: F) -> Self {
194    Self(f)
195  }
196}
197
198impl<F: FnMut()> Drop for ScopeGuard<F> {
199  fn drop(&mut self) {
200    (self.0)()
201  }
202}
203
204trait ResultExt<T> {
205  #[track_caller]
206  fn unwrap_or_display_err(self) -> T;
207}
208
209impl<T, E: Display> ResultExt<T> for Result<T, E> {
210  fn unwrap_or_display_err(self) -> T {
211    match self {
212      Ok(val) => val,
213      Err(err) => panic!("{err}"),
214    }
215  }
216}