use crate::{
types::{any::PyAnyMethods, PyString, PyType},
Bound, Py, PyResult, PyVisit, Python,
};
use std::cell::UnsafeCell;
pub struct GILProtected<T> {
value: T,
}
impl<T> GILProtected<T> {
pub const fn new(value: T) -> Self {
Self { value }
}
pub fn get<'py>(&'py self, _py: Python<'py>) -> &'py T {
&self.value
}
pub fn traverse<'py>(&'py self, _visit: PyVisit<'py>) -> &'py T {
&self.value
}
}
unsafe impl<T> Sync for GILProtected<T> where T: Send {}
#[doc = concat!("[the FAQ section](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/faq.html)")]
#[derive(Default)]
pub struct GILOnceCell<T>(UnsafeCell<Option<T>>);
unsafe impl<T: Send + Sync> Sync for GILOnceCell<T> {}
unsafe impl<T: Send> Send for GILOnceCell<T> {}
impl<T> GILOnceCell<T> {
pub const fn new() -> Self {
Self(UnsafeCell::new(None))
}
#[inline]
pub fn get(&self, _py: Python<'_>) -> Option<&T> {
unsafe { &*self.0.get() }.as_ref()
}
#[inline]
pub fn get_or_init<F>(&self, py: Python<'_>, f: F) -> &T
where
F: FnOnce() -> T,
{
if let Some(value) = self.get(py) {
return value;
}
self.init(py, || Ok::<T, std::convert::Infallible>(f()))
.unwrap()
}
#[inline]
pub fn get_or_try_init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
if let Some(value) = self.get(py) {
return Ok(value);
}
self.init(py, f)
}
#[cold]
fn init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
let value = f()?;
let _ = self.set(py, value);
Ok(self.get(py).unwrap())
}
pub fn get_mut(&mut self) -> Option<&mut T> {
self.0.get_mut().as_mut()
}
pub fn set(&self, _py: Python<'_>, value: T) -> Result<(), T> {
let inner = unsafe { &mut *self.0.get() };
if inner.is_some() {
return Err(value);
}
*inner = Some(value);
Ok(())
}
pub fn take(&mut self) -> Option<T> {
self.0.get_mut().take()
}
pub fn into_inner(self) -> Option<T> {
self.0.into_inner()
}
}
impl<T> GILOnceCell<Py<T>> {
pub fn clone_ref(&self, py: Python<'_>) -> Self {
Self(UnsafeCell::new(self.get(py).map(|ob| ob.clone_ref(py))))
}
}
impl GILOnceCell<Py<PyType>> {
pub(crate) fn get_or_try_init_type_ref<'py>(
&self,
py: Python<'py>,
module_name: &str,
attr_name: &str,
) -> PyResult<&Bound<'py, PyType>> {
self.get_or_try_init(py, || {
let type_object = py
.import_bound(module_name)?
.getattr(attr_name)?
.downcast_into()?;
Ok(type_object.unbind())
})
.map(|ty| ty.bind(py))
}
}
#[macro_export]
macro_rules! intern {
($py: expr, $text: expr) => {{
static INTERNED: $crate::sync::Interned = $crate::sync::Interned::new($text);
INTERNED.get($py)
}};
}
#[doc(hidden)]
pub struct Interned(&'static str, GILOnceCell<Py<PyString>>);
impl Interned {
pub const fn new(value: &'static str) -> Self {
Interned(value, GILOnceCell::new())
}
#[inline]
pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyString> {
self.1
.get_or_init(py, || PyString::intern_bound(py, self.0).into())
.bind(py)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{dict::PyDictMethods, PyDict};
#[test]
fn test_intern() {
Python::with_gil(|py| {
let foo1 = "foo";
let foo2 = intern!(py, "foo");
let foo3 = intern!(py, stringify!(foo));
let dict = PyDict::new_bound(py);
dict.set_item(foo1, 42_usize).unwrap();
assert!(dict.contains(foo2).unwrap());
assert_eq!(
dict.get_item(foo3)
.unwrap()
.unwrap()
.extract::<usize>()
.unwrap(),
42
);
});
}
#[test]
fn test_once_cell() {
Python::with_gil(|py| {
let mut cell = GILOnceCell::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 = GILOnceCell::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));
})
}
}