Gut specialization and implement auto trait
Implement auto trait Implement auto trait Undo oopsie Fix versions Fix CI errors Fix CI Remove more specialization remnants
This commit is contained in:
parent
3c1a0c6b7e
commit
9e29c1058c
|
@ -46,6 +46,7 @@ trybuild = "1.0.49"
|
|||
rustversion = "1.0"
|
||||
# 1.0.0 requires Rust 1.50
|
||||
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
|
||||
send_wrapper = "0.5"
|
||||
serde_json = "1.0.61"
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -73,9 +73,7 @@ This feature enables the `#[pyproto]` macro, which is an alternative (older, soo
|
|||
|
||||
### `nightly`
|
||||
|
||||
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:
|
||||
- `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object supports the Python buffer protocol.
|
||||
- `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type.
|
||||
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use the auto_traits and negative_impls features to fix the `Python::allow_threads` function.
|
||||
|
||||
### `resolve-config`
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@
|
|||
macro_rules! opaque_struct {
|
||||
($name:ident) => {
|
||||
#[repr(C)]
|
||||
pub struct $name([u8; 0]);
|
||||
pub struct $name([u8; 0], ::std::marker::PhantomData<$crate::PyObject>);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! https://github.com/PyO3/pyo3/issues/233
|
||||
//! <https://github.com/PyO3/pyo3/issues/233>
|
||||
//!
|
||||
//! The code below just tries to use the most important code generation paths
|
||||
|
||||
|
|
|
@ -97,38 +97,7 @@ pub trait ToBorrowedObject: ToPyObject {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> ToBorrowedObject for T
|
||||
where
|
||||
T: ToPyObject,
|
||||
{
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "nightly")))]
|
||||
default fn with_borrowed_ptr<F, R>(&self, py: Python, f: F) -> R
|
||||
where
|
||||
F: FnOnce(*mut ffi::PyObject) -> R,
|
||||
{
|
||||
let ptr = self.to_object(py).into_ptr();
|
||||
let result = f(ptr);
|
||||
unsafe {
|
||||
ffi::Py_XDECREF(ptr);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "nightly")))]
|
||||
impl<T> ToBorrowedObject for T
|
||||
where
|
||||
T: ToPyObject + AsPyPointer,
|
||||
{
|
||||
fn with_borrowed_ptr<F, R>(&self, _py: Python, f: F) -> R
|
||||
where
|
||||
F: FnOnce(*mut ffi::PyObject) -> R,
|
||||
{
|
||||
f(self.as_ptr())
|
||||
}
|
||||
}
|
||||
impl<T> ToBorrowedObject for T where T: ToPyObject {}
|
||||
|
||||
/// Defines a conversion from a Rust type to a Python object.
|
||||
///
|
||||
|
|
|
@ -18,37 +18,9 @@ mod min_const_generics {
|
|||
where
|
||||
T: FromPyObject<'a>,
|
||||
{
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
fn extract(obj: &'a PyAny) -> PyResult<Self> {
|
||||
create_array_from_obj(obj)
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
default fn extract(obj: &'a PyAny) -> PyResult<Self> {
|
||||
create_array_from_obj(obj)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "nightly", not(Py_LIMITED_API)))]
|
||||
impl<'source, T, const N: usize> FromPyObject<'source> for [T; N]
|
||||
where
|
||||
for<'a> T: Default + FromPyObject<'a> + crate::buffer::Element,
|
||||
{
|
||||
fn extract(obj: &'source PyAny) -> PyResult<Self> {
|
||||
use crate::AsPyPointer;
|
||||
// first try buffer protocol
|
||||
if unsafe { crate::ffi::PyObject_CheckBuffer(obj.as_ptr()) } == 1 {
|
||||
if let Ok(buf) = crate::buffer::PyBuffer::get(obj) {
|
||||
let mut array = [T::default(); N];
|
||||
if buf.dimensions() == 1 && buf.copy_to_slice(obj.py(), &mut array).is_ok() {
|
||||
buf.release(obj.py());
|
||||
return Ok(array);
|
||||
}
|
||||
buf.release(obj.py());
|
||||
}
|
||||
}
|
||||
create_array_from_obj(obj)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_array_from_obj<'s, T, const N: usize>(obj: &'s PyAny) -> PyResult<[T; N]>
|
||||
|
@ -185,42 +157,11 @@ mod array_impls {
|
|||
where
|
||||
T: Copy + Default + FromPyObject<'a>,
|
||||
{
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
fn extract(obj: &'a PyAny) -> PyResult<Self> {
|
||||
let mut array = [T::default(); $N];
|
||||
extract_sequence_into_slice(obj, &mut array)?;
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
default fn extract(obj: &'a PyAny) -> PyResult<Self> {
|
||||
let mut array = [T::default(); $N];
|
||||
extract_sequence_into_slice(obj, &mut array)?;
|
||||
Ok(array)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<'source, T> FromPyObject<'source> for [T; $N]
|
||||
where
|
||||
for<'a> T: Default + FromPyObject<'a> + crate::buffer::Element,
|
||||
{
|
||||
fn extract(obj: &'source PyAny) -> PyResult<Self> {
|
||||
let mut array = [T::default(); $N];
|
||||
// first try buffer protocol
|
||||
if unsafe { crate::ffi::PyObject_CheckBuffer(obj.as_ptr()) } == 1 {
|
||||
if let Ok(buf) = crate::buffer::PyBuffer::get(obj) {
|
||||
if buf.dimensions() == 1 && buf.copy_to_slice(obj.py(), &mut array).is_ok() {
|
||||
buf.release(obj.py());
|
||||
return Ok(array);
|
||||
}
|
||||
buf.release(obj.py());
|
||||
}
|
||||
}
|
||||
// fall back to sequence protocol
|
||||
extract_sequence_into_slice(obj, &mut array)?;
|
||||
Ok(array)
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ pub struct PyErr {
|
|||
state: UnsafeCell<Option<PyErrState>>,
|
||||
}
|
||||
|
||||
// The inner value is only accessed through ways that require proving the gil is held
|
||||
#[cfg(feature = "nightly")]
|
||||
unsafe impl crate::marker::Ungil for PyErr {}
|
||||
unsafe impl Send for PyErr {}
|
||||
unsafe impl Sync for PyErr {}
|
||||
|
||||
|
|
|
@ -223,6 +223,9 @@ pub unsafe trait PyNativeType: Sized {
|
|||
#[repr(transparent)]
|
||||
pub struct Py<T>(NonNull<ffi::PyObject>, PhantomData<T>);
|
||||
|
||||
// The inner value is only accessed through ways that require proving the gil is held
|
||||
#[cfg(feature = "nightly")]
|
||||
unsafe impl<T> crate::marker::Ungil for Py<T> {}
|
||||
unsafe impl<T> Send for Py<T> {}
|
||||
unsafe impl<T> Sync for Py<T> {}
|
||||
|
||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -1,4 +1,4 @@
|
|||
#![cfg_attr(feature = "nightly", feature(specialization))]
|
||||
#![cfg_attr(feature = "nightly", feature(auto_traits, negative_impls))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
#![cfg_attr(
|
||||
docsrs, // rustdoc:: is not supported on msrv
|
||||
|
@ -94,9 +94,7 @@
|
|||
//!
|
||||
//! ## Unstable features
|
||||
//!
|
||||
//! - `nightly`: Gates some optimizations that rely on
|
||||
//! [`#![feature(specialization)]`](https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md),
|
||||
//! for which you'd also need nightly Rust. You should not use this feature.
|
||||
//! - `nightly`: Uses `#![feature(auto_traits, negative_impls)]` to define [`Ungil`] as an auto trait.
|
||||
//
|
||||
//! ## `rustc` environment flags
|
||||
//!
|
||||
|
@ -283,6 +281,7 @@
|
|||
//! [Python from Rust]: https://github.com/PyO3/pyo3#using-python-from-rust
|
||||
//! [Rust from Python]: https://github.com/PyO3/pyo3#using-rust-from-python
|
||||
//! [Features chapter of the guide]: https://pyo3.rs/latest/features.html#features-reference "Features Reference - PyO3 user guide"
|
||||
//! [`Ungil`]: crate::marker::Ungil
|
||||
pub use crate::class::*;
|
||||
pub use crate::conversion::{
|
||||
AsPyPointer, FromPyObject, FromPyPointer, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto,
|
||||
|
@ -293,12 +292,13 @@ pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult};
|
|||
pub use crate::gil::{prepare_freethreaded_python, with_embedded_python_interpreter};
|
||||
pub use crate::gil::{GILGuard, GILPool};
|
||||
pub use crate::instance::{Py, PyNativeType, PyObject};
|
||||
pub use crate::marker::{Python, PythonVersionInfo};
|
||||
pub use crate::marker::Python;
|
||||
pub use crate::pycell::{PyCell, PyRef, PyRefMut};
|
||||
pub use crate::pyclass::PyClass;
|
||||
pub use crate::pyclass_init::PyClassInitializer;
|
||||
pub use crate::type_object::PyTypeInfo;
|
||||
pub use crate::types::PyAny;
|
||||
pub use crate::version::PythonVersionInfo;
|
||||
|
||||
// Old directory layout, to be rethought?
|
||||
#[cfg(not(feature = "pyproto"))]
|
||||
|
@ -355,7 +355,7 @@ mod gil;
|
|||
#[doc(hidden)]
|
||||
pub mod impl_;
|
||||
mod instance;
|
||||
mod marker;
|
||||
pub mod marker;
|
||||
pub mod marshal;
|
||||
pub mod once_cell;
|
||||
pub mod panic;
|
||||
|
@ -366,6 +366,7 @@ pub mod pyclass_init;
|
|||
|
||||
pub mod type_object;
|
||||
pub mod types;
|
||||
mod version;
|
||||
|
||||
pub use crate::conversions::*;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
/// This macro internally calls [`Python::run`](crate::Python::run) and panics
|
||||
/// if it returns `Err`, after printing the error to stdout.
|
||||
///
|
||||
/// If you need to handle failures, please use [`Python::run`](crate::python::Python::run) instead.
|
||||
/// If you need to handle failures, please use [`Python::run`](crate::marker::Python::run) instead.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
|
|
279
src/marker.rs
279
src/marker.rs
|
@ -2,103 +2,161 @@
|
|||
//
|
||||
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython
|
||||
|
||||
//! Fundamental properties of objects tied to the Python interpreter.
|
||||
//!
|
||||
//! The Python interpreter is not threadsafe. To protect the Python interpreter in multithreaded
|
||||
//! scenarios there is a global lock, the *global interpreter lock* (hereafter referred to as *GIL*)
|
||||
//! that must be held to safely interact with Python objects. This is why in PyO3 when you acquire
|
||||
//! the GIL you get a [`Python`] marker token that carries the *lifetime* of holding the GIL and all
|
||||
//! borrowed references to Python objects carry this lifetime as well. This will statically ensure
|
||||
//! that you can never use Python objects after dropping the lock - if you mess this up it will be
|
||||
//! caught at compile time and your program will fail to compile.
|
||||
//!
|
||||
//! It also supports this pattern that many extension modules employ:
|
||||
//! - Drop the GIL, so that other Python threads can acquire it and make progress themselves
|
||||
//! - Do something independently of the Python interpreter, like IO, a long running calculation or
|
||||
//! awaiting a future
|
||||
//! - Once that is done, reacquire the GIL
|
||||
//!
|
||||
//! That API is provided by [`Python::allow_threads`] and enforced via the [`Ungil`] bound on the
|
||||
//! closure and the return type. This is done by relying on the [`Send`] auto trait. `Ungil` is
|
||||
//! defined as the following:
|
||||
//!
|
||||
//! ```rust
|
||||
//! pub unsafe trait Ungil {}
|
||||
//!
|
||||
//! unsafe impl<T: Send> Ungil for T {}
|
||||
//! ```
|
||||
//!
|
||||
//! In practice this API works quite well, but it comes with some drawbacks:
|
||||
//!
|
||||
//! ## Drawbacks
|
||||
//!
|
||||
//! There is no reason to prevent `!Send` types like [`Rc`] from crossing the closure. After all,
|
||||
//! [`Python::allow_threads`] just lets other Python threads run - it does not itself launch a new
|
||||
//! thread.
|
||||
//!
|
||||
//! ```rust, compile_fail
|
||||
//! # #[cfg(feature = "nightly")]
|
||||
//! # compile_error!("this actually works on nightly")
|
||||
//! use pyo3::prelude::*;
|
||||
//! use std::rc::Rc;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! Python::with_gil(|py| {
|
||||
//! let rc = Rc::new(5);
|
||||
//!
|
||||
//! py.allow_threads(|| {
|
||||
//! // This would actually be fine...
|
||||
//! println!("{:?}", *rc);
|
||||
//! });
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Because we are using `Send` for something it's not quite meant for, other code that
|
||||
//! (correctly) upholds the invariants of [`Send`] can cause problems.
|
||||
//!
|
||||
//! [`SendWrapper`] is one of those. Per its documentation:
|
||||
//!
|
||||
//! > A wrapper which allows you to move around non-Send-types between threads, as long as you
|
||||
//! > access the contained value only from within the original thread and make sure that it is
|
||||
//! > dropped from within the original thread.
|
||||
//!
|
||||
//! This will "work" to smuggle Python references across the closure, because we're not actually
|
||||
//! doing anything with threads:
|
||||
//!
|
||||
//! ```rust, no_run
|
||||
//! use pyo3::prelude::*;
|
||||
//! use pyo3::types::PyString;
|
||||
//! use send_wrapper::SendWrapper;
|
||||
//!
|
||||
//! Python::with_gil(|py| {
|
||||
//! let string = PyString::new(py, "foo");
|
||||
//!
|
||||
//! let wrapped = SendWrapper::new(string);
|
||||
//!
|
||||
//! py.allow_threads(|| {
|
||||
//! # #[cfg(not(feature = "nightly"))]
|
||||
//! # {
|
||||
//! // 💥 Unsound! 💥
|
||||
//! let smuggled: &PyString = *wrapped;
|
||||
//! println!("{:?}", smuggled);
|
||||
//! # }
|
||||
//! });
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! For now the answer to that is "don't do that".
|
||||
//!
|
||||
//! # A proper implementation using an auto trait
|
||||
//!
|
||||
//! However on nightly Rust and when PyO3's `nightly` feature is
|
||||
//! enabled, `Ungil` is defined as the following:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # #[cfg(FALSE)]
|
||||
//! # {
|
||||
//! #![feature(auto_traits, negative_impls)]
|
||||
//!
|
||||
//! pub unsafe auto trait Ungil {}
|
||||
//!
|
||||
//! // It is unimplemented for the `Python` struct and Python objects.
|
||||
//! impl !Ungil for Python<'_> {}
|
||||
//! impl !Ungil for ffi::PyObject {}
|
||||
//!
|
||||
//! // `Py` wraps it in a safe api, so this is OK
|
||||
//! unsafe impl <T> Ungil for Py<T> {}
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! With this feature enabled, the above two examples will start working and not working, respectively.
|
||||
//!
|
||||
//! [`SendWrapper`]: https://docs.rs/send_wrapper/latest/send_wrapper/struct.SendWrapper.html
|
||||
//! [`Rc`]: std::rc::Rc
|
||||
//! [`Py`]: crate::Py
|
||||
use crate::err::{self, PyDowncastError, PyErr, PyResult};
|
||||
use crate::gil::{self, GILGuard, GILPool};
|
||||
use crate::impl_::not_send::NotSend;
|
||||
use crate::type_object::{PyTypeInfo, PyTypeObject};
|
||||
use crate::types::{PyAny, PyDict, PyModule, PyType};
|
||||
use crate::version::PythonVersionInfo;
|
||||
use crate::{ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom};
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::marker::PhantomData;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
/// Represents the major, minor, and patch (if any) versions of this interpreter.
|
||||
/// Types that are safe to access while the GIL is not held.
|
||||
///
|
||||
/// See [Python::version].
|
||||
#[derive(Debug)]
|
||||
pub struct PythonVersionInfo<'py> {
|
||||
/// Python major version (e.g. `3`).
|
||||
pub major: u8,
|
||||
/// Python minor version (e.g. `11`).
|
||||
pub minor: u8,
|
||||
/// Python patch version (e.g. `0`).
|
||||
pub patch: u8,
|
||||
/// Python version suffix, if applicable (e.g. `a0`).
|
||||
pub suffix: Option<&'py str>,
|
||||
}
|
||||
/// # Safety
|
||||
///
|
||||
/// The type must not carry borrowed Python references or, if it does, not allow access to them if
|
||||
/// the GIL is not held.
|
||||
///
|
||||
/// See the [module-level documentation](self) for more information.
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
pub unsafe trait Ungil {}
|
||||
|
||||
impl<'py> PythonVersionInfo<'py> {
|
||||
/// Parses a hard-coded Python interpreter version string (e.g. 3.9.0a4+).
|
||||
///
|
||||
/// Panics if the string is ill-formatted.
|
||||
fn from_str(version_number_str: &'py str) -> Self {
|
||||
fn split_and_parse_number(version_part: &str) -> (u8, Option<&str>) {
|
||||
match version_part.find(|c: char| !c.is_ascii_digit()) {
|
||||
None => (version_part.parse().unwrap(), None),
|
||||
Some(version_part_suffix_start) => {
|
||||
let (version_part, version_part_suffix) =
|
||||
version_part.split_at(version_part_suffix_start);
|
||||
(version_part.parse().unwrap(), Some(version_part_suffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
unsafe impl<T: Send> Ungil for T {}
|
||||
|
||||
let mut parts = version_number_str.split('.');
|
||||
let major_str = parts.next().expect("Python major version missing");
|
||||
let minor_str = parts.next().expect("Python minor version missing");
|
||||
let patch_str = parts.next();
|
||||
assert!(
|
||||
parts.next().is_none(),
|
||||
"Python version string has too many parts"
|
||||
);
|
||||
/// Types that are safe to access while the GIL is not held.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The type must not carry borrowed Python references or, if it does, not allow access to them if
|
||||
/// the GIL is not held.
|
||||
///
|
||||
/// See the [module-level documentation](self) for more information.
|
||||
#[cfg(feature = "nightly")]
|
||||
pub unsafe auto trait Ungil {}
|
||||
|
||||
let major = major_str
|
||||
.parse()
|
||||
.expect("Python major version not an integer");
|
||||
let (minor, suffix) = split_and_parse_number(minor_str);
|
||||
if suffix.is_some() {
|
||||
assert!(patch_str.is_none());
|
||||
return PythonVersionInfo {
|
||||
major,
|
||||
minor,
|
||||
patch: 0,
|
||||
suffix,
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "nightly")]
|
||||
impl !Ungil for Python<'_> {}
|
||||
|
||||
let (patch, suffix) = patch_str.map(split_and_parse_number).unwrap_or_default();
|
||||
PythonVersionInfo {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
suffix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<(u8, u8)> for PythonVersionInfo<'_> {
|
||||
fn eq(&self, other: &(u8, u8)) -> bool {
|
||||
self.major == other.0 && self.minor == other.1
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<(u8, u8, u8)> for PythonVersionInfo<'_> {
|
||||
fn eq(&self, other: &(u8, u8, u8)) -> bool {
|
||||
self.major == other.0 && self.minor == other.1 && self.patch == other.2
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<(u8, u8)> for PythonVersionInfo<'_> {
|
||||
fn partial_cmp(&self, other: &(u8, u8)) -> Option<std::cmp::Ordering> {
|
||||
(self.major, self.minor).partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<(u8, u8, u8)> for PythonVersionInfo<'_> {
|
||||
fn partial_cmp(&self, other: &(u8, u8, u8)) -> Option<std::cmp::Ordering> {
|
||||
(self.major, self.minor, self.patch).partial_cmp(other)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "nightly")]
|
||||
impl !Ungil for ffi::PyObject {}
|
||||
|
||||
/// A marker token that represents holding the GIL.
|
||||
///
|
||||
|
@ -305,12 +363,8 @@ impl<'py> Python<'py> {
|
|||
/// interpreter for some time and have other Python threads around, this will let you run
|
||||
/// Rust-only code while letting those other Python threads make progress.
|
||||
///
|
||||
/// The closure is impermeable to types that are tied to holding the GIL, such as `&`[`PyAny`]
|
||||
/// and its concrete-typed siblings like `&`[`PyString`]. This is achieved via the [`Send`]
|
||||
/// bound on the closure and the return type. This is slightly
|
||||
/// more restrictive than necessary, but it's the most fitting solution available in stable
|
||||
/// Rust. In the future this bound may be relaxed by using an "auto-trait" instead, if
|
||||
/// [auto-traits] ever become a stable feature of the Rust language.
|
||||
/// Only types that implement [`Ungil`] can cross the closure. See the
|
||||
/// [module level documentation](self) for more information.
|
||||
///
|
||||
/// If you need to pass Python objects into the closure you can use [`Py`]`<T>`to create a
|
||||
/// reference independent of the GIL lifetime. However, you cannot do much with those without a
|
||||
|
@ -365,8 +419,8 @@ impl<'py> Python<'py> {
|
|||
/// [Parallelism]: https://pyo3.rs/main/parallelism.html
|
||||
pub fn allow_threads<T, F>(self, f: F) -> T
|
||||
where
|
||||
F: Send + FnOnce() -> T,
|
||||
T: Send,
|
||||
F: Ungil + FnOnce() -> T,
|
||||
T: Ungil,
|
||||
{
|
||||
// Use a guard pattern to handle reacquiring the GIL, so that the GIL will be reacquired
|
||||
// even if `f` panics.
|
||||
|
@ -532,7 +586,7 @@ impl<'py> Python<'py> {
|
|||
/// # use pyo3::Python;
|
||||
/// Python::with_gil(|py| {
|
||||
/// // The full string could be, for example:
|
||||
/// // "3.0a5+ (py3k:63103M, May 12 2008, 00:53:55) \n[GCC 4.2.3]"
|
||||
/// // "3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 19:00:18) [MSC v.1929 64 bit (AMD64)]"
|
||||
/// assert!(py.version().starts_with("3."));
|
||||
/// });
|
||||
/// ```
|
||||
|
@ -563,7 +617,7 @@ impl<'py> Python<'py> {
|
|||
// version number.
|
||||
let version_number_str = version_str.split(' ').next().unwrap_or(version_str);
|
||||
|
||||
PythonVersionInfo::from_str(version_number_str)
|
||||
TryFrom::try_from(version_number_str).unwrap()
|
||||
}
|
||||
|
||||
/// Registers the object in the release pool, and tries to downcast to specific type.
|
||||
|
@ -804,6 +858,8 @@ impl<'py> Python<'py> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::{IntoPyDict, PyList};
|
||||
use crate::Py;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn test_eval() {
|
||||
|
@ -889,36 +945,19 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_python_version_info() {
|
||||
Python::with_gil(|py| {
|
||||
let version = py.version_info();
|
||||
#[cfg(Py_3_7)]
|
||||
assert!(version >= (3, 7));
|
||||
#[cfg(Py_3_7)]
|
||||
assert!(version >= (3, 7, 0));
|
||||
#[cfg(Py_3_8)]
|
||||
assert!(version >= (3, 8));
|
||||
#[cfg(Py_3_8)]
|
||||
assert!(version >= (3, 8, 0));
|
||||
#[cfg(Py_3_9)]
|
||||
assert!(version >= (3, 9));
|
||||
#[cfg(Py_3_9)]
|
||||
assert!(version >= (3, 9, 0));
|
||||
fn test_allow_threads_pass_stuff_in() {
|
||||
let list: Py<PyList> = Python::with_gil(|py| {
|
||||
let list = PyList::new(py, vec!["foo", "bar"]);
|
||||
list.into()
|
||||
});
|
||||
}
|
||||
let mut v = vec![1, 2, 3];
|
||||
let a = Arc::new(String::from("foo"));
|
||||
|
||||
#[test]
|
||||
fn test_python_version_info_parse() {
|
||||
assert!(PythonVersionInfo::from_str("3.5.0a1") >= (3, 5, 0));
|
||||
assert!(PythonVersionInfo::from_str("3.5+") >= (3, 5, 0));
|
||||
assert!(PythonVersionInfo::from_str("3.5+") == (3, 5, 0));
|
||||
assert!(PythonVersionInfo::from_str("3.5+") != (3, 5, 1));
|
||||
assert!(PythonVersionInfo::from_str("3.5.2a1+") < (3, 5, 3));
|
||||
assert!(PythonVersionInfo::from_str("3.5.2a1+") == (3, 5, 2));
|
||||
assert!(PythonVersionInfo::from_str("3.5.2a1+") == (3, 5));
|
||||
assert!(PythonVersionInfo::from_str("3.5+") == (3, 5));
|
||||
assert!(PythonVersionInfo::from_str("3.5.2a1+") < (3, 6));
|
||||
assert!(PythonVersionInfo::from_str("3.5.2a1+") > (3, 4));
|
||||
Python::with_gil(|py| {
|
||||
py.allow_threads(|| {
|
||||
drop((list, &mut v, a));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -269,35 +269,9 @@ impl<'a, T> FromPyObject<'a> for Vec<T>
|
|||
where
|
||||
T: FromPyObject<'a>,
|
||||
{
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
fn extract(obj: &'a PyAny) -> PyResult<Self> {
|
||||
extract_sequence(obj)
|
||||
}
|
||||
#[cfg(feature = "nightly")]
|
||||
default fn extract(obj: &'a PyAny) -> PyResult<Self> {
|
||||
extract_sequence(obj)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "nightly", not(Py_LIMITED_API)))]
|
||||
impl<'source, T> FromPyObject<'source> for Vec<T>
|
||||
where
|
||||
for<'a> T: FromPyObject<'a> + crate::buffer::Element,
|
||||
{
|
||||
fn extract(obj: &'source PyAny) -> PyResult<Self> {
|
||||
// first try buffer protocol
|
||||
if let Ok(buf) = crate::buffer::PyBuffer::get(obj) {
|
||||
if buf.dimensions() == 1 {
|
||||
if let Ok(v) = buf.to_vec(obj.py()) {
|
||||
buf.release(obj.py());
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
buf.release(obj.py());
|
||||
}
|
||||
// fall back to sequence protocol
|
||||
extract_sequence(obj)
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_sequence<'s, T>(obj: &'s PyAny) -> PyResult<Vec<T>>
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
/// Represents the major, minor, and patch (if any) versions of this interpreter.
|
||||
///
|
||||
/// This struct is usually created with [`Python::version`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
/// ```rust
|
||||
/// # use pyo3::Python;
|
||||
/// Python::with_gil(|py| {
|
||||
/// // PyO3 supports Python 3.7 and up.
|
||||
/// assert!(py.version_info() >= (3, 7));
|
||||
/// assert!(py.version_info() >= (3, 7, 0));
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// [`Python::version`]: crate::marker::Python::version
|
||||
#[derive(Debug)]
|
||||
pub struct PythonVersionInfo<'py> {
|
||||
/// Python major version (e.g. `3`).
|
||||
pub major: u8,
|
||||
/// Python minor version (e.g. `11`).
|
||||
pub minor: u8,
|
||||
/// Python patch version (e.g. `0`).
|
||||
pub patch: u8,
|
||||
/// Python version suffix, if applicable (e.g. `a0`).
|
||||
pub suffix: Option<&'py str>,
|
||||
}
|
||||
|
||||
impl<'py> TryFrom<&'py str> for PythonVersionInfo<'py> {
|
||||
type Error = &'static str;
|
||||
/// Parses a hard-coded Python interpreter version string (e.g. 3.9.0a4+).
|
||||
fn try_from(version_number_str: &'py str) -> Result<Self, Self::Error> {
|
||||
fn split_and_parse_number(version_part: &str) -> (u8, Option<&str>) {
|
||||
match version_part.find(|c: char| !c.is_ascii_digit()) {
|
||||
None => (version_part.parse().unwrap(), None),
|
||||
Some(version_part_suffix_start) => {
|
||||
let (version_part, version_part_suffix) =
|
||||
version_part.split_at(version_part_suffix_start);
|
||||
(version_part.parse().unwrap(), Some(version_part_suffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut parts = version_number_str.split('.');
|
||||
let major_str = parts.next().ok_or("Python major version missing")?;
|
||||
let minor_str = parts.next().ok_or("Python minor version missing")?;
|
||||
let patch_str = parts.next();
|
||||
if parts.next().is_some() {
|
||||
return Err("Python version string has too many parts");
|
||||
};
|
||||
|
||||
let major = major_str
|
||||
.parse()
|
||||
.map_err(|_| "Python major version not an integer")?;
|
||||
let (minor, suffix) = split_and_parse_number(minor_str);
|
||||
if suffix.is_some() {
|
||||
assert!(patch_str.is_none());
|
||||
return Ok(PythonVersionInfo {
|
||||
major,
|
||||
minor,
|
||||
patch: 0,
|
||||
suffix,
|
||||
});
|
||||
}
|
||||
|
||||
let (patch, suffix) = patch_str.map(split_and_parse_number).unwrap_or_default();
|
||||
Ok(PythonVersionInfo {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
suffix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<(u8, u8)> for PythonVersionInfo<'_> {
|
||||
fn eq(&self, other: &(u8, u8)) -> bool {
|
||||
self.major == other.0 && self.minor == other.1
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<(u8, u8, u8)> for PythonVersionInfo<'_> {
|
||||
fn eq(&self, other: &(u8, u8, u8)) -> bool {
|
||||
self.major == other.0 && self.minor == other.1 && self.patch == other.2
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<(u8, u8)> for PythonVersionInfo<'_> {
|
||||
fn partial_cmp(&self, other: &(u8, u8)) -> Option<std::cmp::Ordering> {
|
||||
(self.major, self.minor).partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<(u8, u8, u8)> for PythonVersionInfo<'_> {
|
||||
fn partial_cmp(&self, other: &(u8, u8, u8)) -> Option<std::cmp::Ordering> {
|
||||
(self.major, self.minor, self.patch).partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Python;
|
||||
#[test]
|
||||
fn test_python_version_info() {
|
||||
Python::with_gil(|py| {
|
||||
let version = py.version_info();
|
||||
#[cfg(Py_3_7)]
|
||||
assert!(version >= (3, 7));
|
||||
#[cfg(Py_3_7)]
|
||||
assert!(version >= (3, 7, 0));
|
||||
#[cfg(Py_3_8)]
|
||||
assert!(version >= (3, 8));
|
||||
#[cfg(Py_3_8)]
|
||||
assert!(version >= (3, 8, 0));
|
||||
#[cfg(Py_3_9)]
|
||||
assert!(version >= (3, 9));
|
||||
#[cfg(Py_3_9)]
|
||||
assert!(version >= (3, 9, 0));
|
||||
#[cfg(Py_3_10)]
|
||||
assert!(version >= (3, 10));
|
||||
#[cfg(Py_3_10)]
|
||||
assert!(version >= (3, 10, 0));
|
||||
#[cfg(Py_3_11)]
|
||||
assert!(version >= (3, 11));
|
||||
#[cfg(Py_3_11)]
|
||||
assert!(version >= (3, 11, 0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_python_version_info_parse() {
|
||||
assert!(PythonVersionInfo::try_from("3.5.0a1").unwrap() >= (3, 5, 0));
|
||||
assert!(PythonVersionInfo::try_from("3.5+").unwrap() >= (3, 5, 0));
|
||||
assert!(PythonVersionInfo::try_from("3.5+").unwrap() == (3, 5, 0));
|
||||
assert!(PythonVersionInfo::try_from("3.5+").unwrap() != (3, 5, 1));
|
||||
assert!(PythonVersionInfo::try_from("3.5.2a1+").unwrap() < (3, 5, 3));
|
||||
assert!(PythonVersionInfo::try_from("3.5.2a1+").unwrap() == (3, 5, 2));
|
||||
assert!(PythonVersionInfo::try_from("3.5.2a1+").unwrap() == (3, 5));
|
||||
assert!(PythonVersionInfo::try_from("3.5+").unwrap() == (3, 5));
|
||||
assert!(PythonVersionInfo::try_from("3.5.2a1+").unwrap() < (3, 6));
|
||||
assert!(PythonVersionInfo::try_from("3.5.2a1+").unwrap() > (3, 4));
|
||||
}
|
||||
}
|
|
@ -15,8 +15,10 @@ fn test_compile_errors() {
|
|||
let _ = std::panic::catch_unwind(_test_compile_errors);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
fn _test_compile_errors() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
t.compile_fail("tests/ui/invalid_macro_args.rs");
|
||||
t.compile_fail("tests/ui/invalid_need_module_arg_position.rs");
|
||||
t.compile_fail("tests/ui/invalid_property_args.rs");
|
||||
|
@ -71,6 +73,8 @@ fn _test_compile_errors() {
|
|||
t.compile_fail("tests/ui/invalid_pymethods.rs");
|
||||
t.compile_fail("tests/ui/missing_clone.rs");
|
||||
t.compile_fail("tests/ui/not_send.rs");
|
||||
t.compile_fail("tests/ui/not_send2.rs");
|
||||
t.compile_fail("tests/ui/not_send3.rs");
|
||||
#[cfg(Py_LIMITED_API)]
|
||||
t.compile_fail("tests/ui/abi3_nativetype_inheritance.rs");
|
||||
}
|
||||
|
@ -78,3 +82,12 @@ fn _test_compile_errors() {
|
|||
#[rustversion::before(1.58)]
|
||||
fn tests_rust_1_58(_t: &trybuild::TestCases) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
fn _test_compile_errors() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
t.compile_fail("tests/ui/not_send_auto_trait.rs");
|
||||
t.compile_fail("tests/ui/not_send_auto_trait2.rs");
|
||||
t.compile_fail("tests/ui/send_wrapper.rs");
|
||||
}
|
||||
|
|
|
@ -12,8 +12,9 @@ error[E0277]: `*mut pyo3::Python<'static>` cannot be shared between threads safe
|
|||
= note: required because it appears within the type `pyo3::Python<'_>`
|
||||
= note: required because of the requirements on the impl of `Send` for `&pyo3::Python<'_>`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/ui/not_send.rs:4:22: 4:38]`
|
||||
= note: required because of the requirements on the impl of `Ungil` for `[closure@$DIR/tests/ui/not_send.rs:4:22: 4:38]`
|
||||
note: required by a bound in `pyo3::Python::<'py>::allow_threads`
|
||||
--> src/python.rs
|
||||
--> src/marker.rs
|
||||
|
|
||||
| F: Send + FnOnce() -> T,
|
||||
| ^^^^ required by this bound in `pyo3::Python::<'py>::allow_threads`
|
||||
| F: Ungil + FnOnce() -> T,
|
||||
| ^^^^^ required by this bound in `pyo3::Python::<'py>::allow_threads`
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyString;
|
||||
|
||||
fn main() {
|
||||
Python::with_gil(|py| {
|
||||
let string = PyString::new(py, "foo");
|
||||
|
||||
py.allow_threads(|| {
|
||||
println!("{:?}", string);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
error[E0277]: `UnsafeCell<PyObject>` cannot be shared between threads safely
|
||||
--> tests/ui/not_send2.rs:8:12
|
||||
|
|
||||
8 | py.allow_threads(|| {
|
||||
| ^^^^^^^^^^^^^ `UnsafeCell<PyObject>` cannot be shared between threads safely
|
||||
|
|
||||
= help: within `&PyString`, the trait `Sync` is not implemented for `UnsafeCell<PyObject>`
|
||||
= note: required because it appears within the type `PyAny`
|
||||
= note: required because it appears within the type `PyString`
|
||||
= note: required because it appears within the type `&PyString`
|
||||
= note: required because of the requirements on the impl of `Send` for `&&PyString`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/ui/not_send2.rs:8:26: 10:10]`
|
||||
= note: required because of the requirements on the impl of `Ungil` for `[closure@$DIR/tests/ui/not_send2.rs:8:26: 10:10]`
|
||||
note: required by a bound in `pyo3::Python::<'py>::allow_threads`
|
||||
--> src/marker.rs
|
||||
|
|
||||
| F: Ungil + FnOnce() -> T,
|
||||
| ^^^^^ required by this bound in `pyo3::Python::<'py>::allow_threads`
|
|
@ -0,0 +1,12 @@
|
|||
use pyo3::prelude::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn main() {
|
||||
Python::with_gil(|py| {
|
||||
let rc = Rc::new(5);
|
||||
|
||||
py.allow_threads(|| {
|
||||
println!("{:?}", rc);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
error[E0277]: `Rc<i32>` cannot be shared between threads safely
|
||||
--> tests/ui/not_send3.rs:8:12
|
||||
|
|
||||
8 | py.allow_threads(|| {
|
||||
| ^^^^^^^^^^^^^ `Rc<i32>` cannot be shared between threads safely
|
||||
|
|
||||
= help: the trait `Sync` is not implemented for `Rc<i32>`
|
||||
= note: required because of the requirements on the impl of `Send` for `&Rc<i32>`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/ui/not_send3.rs:8:26: 10:10]`
|
||||
= note: required because of the requirements on the impl of `Ungil` for `[closure@$DIR/tests/ui/not_send3.rs:8:26: 10:10]`
|
||||
note: required by a bound in `pyo3::Python::<'py>::allow_threads`
|
||||
--> src/marker.rs
|
||||
|
|
||||
| F: Ungil + FnOnce() -> T,
|
||||
| ^^^^^ required by this bound in `pyo3::Python::<'py>::allow_threads`
|
|
@ -0,0 +1,11 @@
|
|||
use pyo3::prelude::*;
|
||||
|
||||
fn test_not_send_allow_threads(py: Python) {
|
||||
py.allow_threads(|| { drop(py); });
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Python::with_gil(|py| {
|
||||
test_not_send_allow_threads(py);
|
||||
})
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
error[E0277]: the trait bound `pyo3::Python<'_>: Ungil` is not satisfied in `[closure@$DIR/tests/ui/not_send_auto_trait.rs:4:22: 4:38]`
|
||||
--> tests/ui/not_send_auto_trait.rs:4:8
|
||||
|
|
||||
4 | py.allow_threads(|| { drop(py); });
|
||||
| ^^^^^^^^^^^^^ ---------------- within this `[closure@$DIR/tests/ui/not_send_auto_trait.rs:4:22: 4:38]`
|
||||
| |
|
||||
| within `[closure@$DIR/tests/ui/not_send_auto_trait.rs:4:22: 4:38]`, the trait `Ungil` is not implemented for `pyo3::Python<'_>`
|
||||
|
|
||||
= note: required because it appears within the type `&pyo3::Python<'_>`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/ui/not_send_auto_trait.rs:4:22: 4:38]`
|
||||
note: required by a bound in `pyo3::Python::<'py>::allow_threads`
|
||||
--> src/marker.rs
|
||||
|
|
||||
| F: Ungil + FnOnce() -> T,
|
||||
| ^^^^^ required by this bound in `pyo3::Python::<'py>::allow_threads`
|
|
@ -0,0 +1,12 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyString;
|
||||
|
||||
fn main() {
|
||||
Python::with_gil(|py| {
|
||||
let string = PyString::new(py, "foo");
|
||||
|
||||
py.allow_threads(|| {
|
||||
println!("{:?}", string);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
error[E0277]: the trait bound `PyObject: Ungil` is not satisfied in `[closure@$DIR/tests/ui/not_send_auto_trait2.rs:8:26: 10:10]`
|
||||
--> tests/ui/not_send_auto_trait2.rs:8:12
|
||||
|
|
||||
8 | py.allow_threads(|| {
|
||||
| ____________^^^^^^^^^^^^^_-
|
||||
| | |
|
||||
| | within `[closure@$DIR/tests/ui/not_send_auto_trait2.rs:8:26: 10:10]`, the trait `Ungil` is not implemented for `PyObject`
|
||||
9 | | println!("{:?}", string);
|
||||
10 | | });
|
||||
| |_________- within this `[closure@$DIR/tests/ui/not_send_auto_trait2.rs:8:26: 10:10]`
|
||||
|
|
||||
= note: required because it appears within the type `UnsafeCell<PyObject>`
|
||||
= note: required because it appears within the type `PyAny`
|
||||
= note: required because it appears within the type `PyString`
|
||||
= note: required because it appears within the type `&PyString`
|
||||
= note: required because it appears within the type `&&PyString`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/ui/not_send_auto_trait2.rs:8:26: 10:10]`
|
||||
note: required by a bound in `pyo3::Python::<'py>::allow_threads`
|
||||
--> src/marker.rs
|
||||
|
|
||||
| F: Ungil + FnOnce() -> T,
|
||||
| ^^^^^ required by this bound in `pyo3::Python::<'py>::allow_threads`
|
|
@ -0,0 +1,16 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyString;
|
||||
use send_wrapper::SendWrapper;
|
||||
|
||||
fn main() {
|
||||
Python::with_gil(|py| {
|
||||
let string = PyString::new(py, "foo");
|
||||
|
||||
let wrapped = SendWrapper::new(string);
|
||||
|
||||
py.allow_threads(|| {
|
||||
let smuggled: &PyString = *wrapped;
|
||||
println!("{:?}", smuggled);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
error[E0277]: the trait bound `PyObject: Ungil` is not satisfied in `[closure@$DIR/tests/ui/send_wrapper.rs:11:26: 14:10]`
|
||||
--> tests/ui/send_wrapper.rs:11:12
|
||||
|
|
||||
11 | py.allow_threads(|| {
|
||||
| ____________^^^^^^^^^^^^^_-
|
||||
| | |
|
||||
| | within `[closure@$DIR/tests/ui/send_wrapper.rs:11:26: 14:10]`, the trait `Ungil` is not implemented for `PyObject`
|
||||
12 | | let smuggled: &PyString = *wrapped;
|
||||
13 | | println!("{:?}", smuggled);
|
||||
14 | | });
|
||||
| |_________- within this `[closure@$DIR/tests/ui/send_wrapper.rs:11:26: 14:10]`
|
||||
|
|
||||
= note: required because it appears within the type `UnsafeCell<PyObject>`
|
||||
= note: required because it appears within the type `PyAny`
|
||||
= note: required because it appears within the type `PyString`
|
||||
= note: required because it appears within the type `&PyString`
|
||||
= note: required because it appears within the type `*mut &PyString`
|
||||
= note: required because it appears within the type `SendWrapper<&PyString>`
|
||||
= note: required because it appears within the type `&SendWrapper<&PyString>`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/ui/send_wrapper.rs:11:26: 14:10]`
|
||||
note: required by a bound in `pyo3::Python::<'py>::allow_threads`
|
||||
--> src/marker.rs
|
||||
|
|
||||
| F: Ungil + FnOnce() -> T,
|
||||
| ^^^^^ required by this bound in `pyo3::Python::<'py>::allow_threads`
|
Loading…
Reference in New Issue