use crate::{
internal::state::SuspendAttach, types::any::PyAnyMethods, Bound, Py, PyResult, PyTypeCheck,
Python,
};
#[derive(Default)]
pub struct PyOnceLock<T> {
inner: once_cell::sync::OnceCell<T>,
}
impl<T> PyOnceLock<T> {
pub const fn new() -> Self {
Self {
inner: once_cell::sync::OnceCell::new(),
}
}
#[inline]
pub fn get(&self, _py: Python<'_>) -> Option<&T> {
self.inner.get()
}
#[inline]
pub fn get_or_init<F>(&self, py: Python<'_>, f: F) -> &T
where
F: FnOnce() -> T,
{
self.inner
.get()
.unwrap_or_else(|| init_once_cell_py_attached(&self.inner, py, f))
}
pub fn get_or_try_init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
self.inner
.get()
.map_or_else(|| try_init_once_cell_py_attached(&self.inner, py, f), Ok)
}
pub fn get_mut(&mut self) -> Option<&mut T> {
self.inner.get_mut()
}
pub fn set(&self, _py: Python<'_>, value: T) -> Result<(), T> {
self.inner.set(value)
}
pub fn take(&mut self) -> Option<T> {
self.inner.take()
}
pub fn into_inner(self) -> Option<T> {
self.inner.into_inner()
}
}
impl<T> PyOnceLock<Py<T>> {
pub fn clone_ref(&self, py: Python<'_>) -> Self {
let cloned = PyOnceLock::new();
if let Some(value) = self.get(py) {
let _ = cloned.set(py, value.clone_ref(py));
}
cloned
}
}
impl<T> PyOnceLock<Py<T>>
where
T: PyTypeCheck,
{
pub fn import<'py>(
&self,
py: Python<'py>,
module_name: &str,
attr_name: &str,
) -> PyResult<&Bound<'py, T>> {
self.get_or_try_init(py, || {
let type_object = py.import(module_name)?.getattr(attr_name)?.cast_into()?;
Ok(type_object.unbind())
})
.map(|ty| ty.bind(py))
}
}
#[cold]
fn init_once_cell_py_attached<'a, F, T>(
cell: &'a once_cell::sync::OnceCell<T>,
_py: Python<'_>,
f: F,
) -> &'a T
where
F: FnOnce() -> T,
{
let ts_guard = unsafe { SuspendAttach::new() };
cell.get_or_init(move || {
drop(ts_guard);
f()
})
}
#[cold]
fn try_init_once_cell_py_attached<'a, F, T, E>(
cell: &'a once_cell::sync::OnceCell<T>,
_py: Python<'_>,
f: F,
) -> Result<&'a T, E>
where
F: FnOnce() -> Result<T, E>,
{
let ts_guard = unsafe { SuspendAttach::new() };
cell.get_or_try_init(move || {
drop(ts_guard);
f()
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_once_cell() {
Python::attach(|py| {
let mut cell = PyOnceLock::new();
assert!(cell.get(py).is_none());
assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5));
assert!(cell.get(py).is_none());
assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2));
assert_eq!(cell.get(py), Some(&2));
assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2));
assert_eq!(cell.take(), Some(2));
assert_eq!(cell.into_inner(), None);
let cell_py = PyOnceLock::new();
assert!(cell_py.clone_ref(py).get(py).is_none());
cell_py.get_or_init(py, || py.None());
assert!(cell_py.clone_ref(py).get(py).unwrap().is_none(py));
})
}
#[test]
fn test_once_cell_drop() {
#[derive(Debug)]
struct RecordDrop<'a>(&'a mut bool);
impl Drop for RecordDrop<'_> {
fn drop(&mut self) {
*self.0 = true;
}
}
Python::attach(|py| {
let mut dropped = false;
let cell = PyOnceLock::new();
cell.set(py, RecordDrop(&mut dropped)).unwrap();
let drop_container = cell.get(py).unwrap();
assert!(!*drop_container.0);
drop(cell);
assert!(dropped);
});
}
}