Skip to main content

pyo3/types/
function.rs

1use crate::ffi_ptr_ext::FfiPtrExt;
2use crate::impl_::pyfunction::create_py_c_function;
3use crate::platform::prelude::*;
4use crate::py_result_ext::PyResultExt;
5use crate::types::capsule::PyCapsuleMethods;
6use crate::{
7    ffi,
8    impl_::pymethods::{self, PyMethodDef},
9    types::{PyCapsule, PyDict, PyModule, PyTuple},
10};
11#[cfg(Py_LIMITED_API)]
12use crate::{
13    sync::PyOnceLock,
14    types::{PyType, PyTypeMethods},
15    Py,
16};
17use crate::{Bound, PyAny, PyResult, Python};
18use core::cell::UnsafeCell;
19use core::ffi::CStr;
20use core::ptr::NonNull;
21
22/// Represents a builtin Python function object.
23///
24/// Values of this type are accessed via PyO3's smart pointers, e.g. as
25/// [`Py<PyCFunction>`][crate::Py] or [`Bound<'py, PyCFunction>`][Bound].
26#[repr(transparent)]
27pub struct PyCFunction(PyAny);
28
29#[cfg(not(RustPython))]
30pyobject_native_type_core!(PyCFunction, pyobject_native_static_type_object!(ffi::PyCFunction_Type), "builtins", "builtin_function_or_method", #checkfunction=ffi::PyCFunction_Check);
31
32#[cfg(RustPython)]
33pyobject_native_type_core!(
34    PyCFunction,
35    |py| {
36        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
37        TYPE.import(py, "builtins", "builtin_function_or_method")
38            .unwrap()
39            .as_type_ptr()
40    },
41    "builtins",
42    "builtin_function_or_method",
43    #checkfunction=ffi::PyCFunction_Check
44);
45
46impl PyCFunction {
47    /// Create a new built-in function with keywords (*args and/or **kwargs).
48    ///
49    /// To create `name` and `doc` static strings on Rust versions older than 1.77 (which added c"" literals),
50    /// use the [`c_str!`](crate::ffi::c_str) macro.
51    pub fn new_with_keywords<'py>(
52        py: Python<'py>,
53        fun: ffi::PyCFunctionWithKeywords,
54        name: &'static CStr,
55        doc: &'static CStr,
56        module: Option<&Bound<'py, PyModule>>,
57    ) -> PyResult<Bound<'py, Self>> {
58        let def = PyMethodDef::cfunction_with_keywords(name, fun, doc).into_raw();
59        // FIXME: stop leaking the def
60        let def = Box::leak(Box::new(def));
61        // Safety: def is static
62        unsafe { create_py_c_function(py, def, module) }
63    }
64
65    /// Create a new built-in function which takes no arguments.
66    ///
67    /// To create `name` and `doc` static strings on Rust versions older than 1.77 (which added c"" literals),
68    /// use the [`c_str!`](crate::ffi::c_str) macro.
69    pub fn new<'py>(
70        py: Python<'py>,
71        fun: ffi::PyCFunction,
72        name: &'static CStr,
73        doc: &'static CStr,
74        module: Option<&Bound<'py, PyModule>>,
75    ) -> PyResult<Bound<'py, Self>> {
76        let def = PyMethodDef::noargs(name, fun, doc).into_raw();
77        // FIXME: stop leaking the def
78        let def = Box::leak(Box::new(def));
79        // Safety: def is static
80        unsafe { create_py_c_function(py, def, module) }
81    }
82
83    /// Create a new function from a closure.
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// # use pyo3::prelude::*;
89    /// # use pyo3::{py_run, types::{PyCFunction, PyDict, PyTuple}};
90    ///
91    /// Python::attach(|py| {
92    ///     let add_one = |args: &Bound<'_, PyTuple>, _kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
93    ///         let i = args.extract::<(i64,)>()?.0;
94    ///         Ok(i+1)
95    ///     };
96    ///     let add_one = PyCFunction::new_closure(py, None, None, add_one).unwrap();
97    ///     py_run!(py, add_one, "assert add_one(42) == 43");
98    /// });
99    /// ```
100    pub fn new_closure<'py, F, R>(
101        py: Python<'py>,
102        name: Option<&'static CStr>,
103        doc: Option<&'static CStr>,
104        closure: F,
105    ) -> PyResult<Bound<'py, Self>>
106    where
107        F: Fn(&Bound<'_, PyTuple>, Option<&Bound<'_, PyDict>>) -> R + Send + Sync + 'static,
108        for<'p> R: crate::impl_::callback::IntoPyCallbackOutput<'p, *mut ffi::PyObject>,
109    {
110        let name = name.unwrap_or(c"pyo3-closure");
111        let doc = doc.unwrap_or(c"");
112        let method_def =
113            pymethods::PyMethodDef::cfunction_with_keywords(name, run_closure::<F, R>, doc);
114        let def = method_def.into_raw();
115
116        let capsule = PyCapsule::new_with_value(
117            py,
118            ClosureDestructor::<F> {
119                closure,
120                def: UnsafeCell::new(def),
121            },
122            CLOSURE_CAPSULE_NAME,
123        )?;
124
125        let data: NonNull<ClosureDestructor<F>> =
126            capsule.pointer_checked(Some(CLOSURE_CAPSULE_NAME))?.cast();
127
128        // SAFETY: The capsule has just been created with the value, and will exist as long as
129        // the function object exists.
130        let method_def = unsafe { data.as_ref().def.get() };
131
132        // SAFETY: The arguments to `PyCFunction_NewEx` are valid, we are attached to the
133        // interpreter and we know the function either returns a new reference or errors.
134        unsafe {
135            ffi::PyCFunction_NewEx(method_def, capsule.as_ptr(), core::ptr::null_mut())
136                .assume_owned_or_err(py)
137                .cast_into_unchecked()
138        }
139    }
140}
141
142static CLOSURE_CAPSULE_NAME: &CStr = c"pyo3-closure";
143
144unsafe extern "C" fn run_closure<F, R>(
145    capsule_ptr: *mut ffi::PyObject,
146    args: *mut ffi::PyObject,
147    kwargs: *mut ffi::PyObject,
148) -> *mut ffi::PyObject
149where
150    F: Fn(&Bound<'_, PyTuple>, Option<&Bound<'_, PyDict>>) -> R + Send + 'static,
151    for<'py> R: crate::impl_::callback::IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
152{
153    unsafe {
154        crate::impl_::trampoline::cfunction_with_keywords::inner(
155            capsule_ptr,
156            args,
157            kwargs,
158            |py, capsule_ptr, args, kwargs| {
159                let boxed_fn: &ClosureDestructor<F> =
160                    &*(ffi::PyCapsule_GetPointer(capsule_ptr, CLOSURE_CAPSULE_NAME.as_ptr())
161                        as *mut ClosureDestructor<F>);
162                let args = Bound::ref_from_ptr(py, &args).cast_unchecked::<PyTuple>();
163                let kwargs = Bound::ref_from_ptr_or_opt(py, &kwargs)
164                    .as_ref()
165                    .map(|b| b.cast_unchecked::<PyDict>());
166                let result = (boxed_fn.closure)(args, kwargs);
167                crate::impl_::callback::convert(py, result)
168            },
169        )
170    }
171}
172
173struct ClosureDestructor<F> {
174    closure: F,
175    // Wrapped in UnsafeCell because Python C-API wants a *mut pointer
176    // to this member.
177    def: UnsafeCell<ffi::PyMethodDef>,
178}
179
180// Safety: F is send and none of the fields are ever mutated
181unsafe impl<F: Send> Send for ClosureDestructor<F> {}
182
183/// Represents a Python function object.
184///
185/// Values of this type are accessed via PyO3's smart pointers, e.g. as
186/// [`Py<PyFunction>`][crate::Py] or [`Bound<'py, PyFunction>`][Bound].
187#[repr(transparent)]
188pub struct PyFunction(PyAny);
189
190#[cfg(not(Py_LIMITED_API))]
191pyobject_native_type_core!(PyFunction, pyobject_native_static_type_object!(ffi::PyFunction_Type), "builtins", "function", #checkfunction=ffi::PyFunction_Check);
192
193#[cfg(Py_LIMITED_API)]
194pyobject_native_type_core!(
195    PyFunction,
196    |py| {
197        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
198        TYPE.import(py, "types", "FunctionType")
199            .unwrap()
200            .as_type_ptr()
201    },
202    "builtins",
203    "function"
204);