Skip to main content

pyforge_ffi/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! Raw FFI declarations for Python's C API.
3//!
4//! PyForge can be used to write native Python modules or run Python code and modules from Rust.
5//!
6//! This crate just provides low level bindings to the Python interpreter.
7//! It is meant for advanced users only - regular PyForge users shouldn't
8//! need to interact with this crate at all.
9//!
10//! The contents of this crate are not documented here, as it would entail
11//! basically copying the documentation from CPython. Consult the [Python/C API Reference
12//! Manual][capi] for up-to-date documentation.
13//!
14//! # Safety
15//!
16//! The functions in this crate lack individual safety documentation, but
17//! generally the following apply:
18//! - Pointer arguments have to point to a valid Python object of the correct type,
19//! although null pointers are sometimes valid input.
20//! - The vast majority can only be used safely while the thread is attached to the Python interpreter.
21//! - Some functions have additional safety requirements, consult the
22//! [Python/C API Reference Manual][capi]
23//! for more information.
24//!
25//!
26//! # Feature flags
27//!
28//! PyForge uses [feature flags] to enable you to opt-in to additional functionality. For a detailed
29//! description, see the [Features chapter of the guide].
30//!
31//! ## Optional feature flags
32//!
33//! The following features customize PyForge's behavior:
34//!
35//! - `abi3`: Restricts PyForge's API to a subset of the full Python API which is guaranteed by
36//! [PEP 384] to be forward-compatible with future Python versions.
37//!
38//! ## `rustc` environment flags
39//!
40//! PyForge uses `rustc`'s `--cfg` flags to enable or disable code used for different Python versions.
41//! If you want to do this for your own crate, you can do so with the [`pyforge-build-config`] crate.
42//!
43//! - `Py_3_8`, `Py_3_9`, `Py_3_10`, `Py_3_11`, `Py_3_12`, `Py_3_13`, `Py_3_14`: Marks code that is
44//!    only enabled when compiling for a given minimum Python version.
45//! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled.
46//! - `Py_GIL_DISABLED`: Marks code that runs only in the free-threaded build of CPython.
47//! - PyForge: PyPy and GraalPy are not supported. CPython only.
48//!
49//! Additionally, you can query for the values `Py_DEBUG`, `Py_REF_DEBUG`,
50//! `Py_TRACE_REFS`, and `COUNT_ALLOCS` from `py_sys_config` to query for the
51//! corresponding C build-time defines. For example, to conditionally define
52//! debug code using `Py_DEBUG`, you could do:
53//!
54//! ```rust,ignore
55//! #[cfg(py_sys_config = "Py_DEBUG")]
56//! println!("only runs if python was compiled with Py_DEBUG")
57//! ```
58//!
59//! To use these attributes, add [`pyforge-build-config`] as a build dependency in
60//! your `Cargo.toml`:
61//!
62//! ```toml
63//! [build-dependencies]
64#![doc = concat!("pyforge-build-config =\"", env!("CARGO_PKG_VERSION"),  "\"")]
65//! ```
66//!
67//! And then either create a new `build.rs` file in the project root or modify
68//! the existing `build.rs` file to call `use_pyo3_cfgs()`:
69//!
70//! ```rust,ignore
71//! fn main() {
72//!     pyforge_build_config::use_pyo3_cfgs();
73//! }
74//! ```
75//!
76//! # Minimum supported Rust and Python versions
77//!
78//! `pyforge-ffi` supports the following Python distributions:
79//!   - CPython 3.8 or greater
80//!   - CPython 3.11+ only (PyPy and GraalPy are not supported)
81//!
82//! # Example: Building Python Native modules
83//!
84//! PyForge can be used to generate a native Python module. The easiest way to try this out for the
85//! first time is to use [`maturin`]. `maturin` is a tool for building and publishing Rust-based
86//! Python packages with minimal configuration. The following steps set up some files for an example
87//! Python module, install `maturin`, and then show how to build and import the Python module.
88//!
89//! First, create a new folder (let's call it `string_sum`) containing the following two files:
90//!
91//! **`Cargo.toml`**
92//!
93//! ```toml
94//! [lib]
95//! name = "string_sum"
96//! # "cdylib" is necessary to produce a shared library for Python to import from.
97//! #
98//! # Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able
99//! # to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.:
100//! # crate-type = ["cdylib", "rlib"]
101//! crate-type = ["cdylib"]
102//!
103//! [dependencies]
104#![doc = concat!("pyforge-ffi = \"", env!("CARGO_PKG_VERSION"),  "\"")]
105//!
106//! [build-dependencies]
107//! # This is only necessary if you need to configure your build based on
108//! # the Python version or the compile-time configuration for the interpreter.
109#![doc = concat!("pyforge_build_config = \"", env!("CARGO_PKG_VERSION"),  "\"")]
110//! ```
111//!
112//! If you need to use conditional compilation based on Python version or how
113//! Python was compiled, you need to add `pyforge-build-config` as a
114//! `build-dependency` in your `Cargo.toml` as in the example above and either
115//! create a new `build.rs` file or modify an existing one so that
116//! `pyforge_build_config::use_pyo3_cfgs()` gets called at build time:
117//!
118//! **`build.rs`**
119//! ```rust,ignore
120//! fn main() {
121//!     pyforge_build_config::use_pyo3_cfgs()
122//! }
123//! ```
124//!
125//! **`src/lib.rs`**
126//! ```rust,no_run
127//! #[cfg(Py_3_15)]
128//! use std::ffi::c_void;
129//! use std::ffi::{c_char, c_long};
130//! use std::ptr;
131//!
132//! use pyforge_ffi::*;
133//!
134//! #[cfg(not(Py_3_15))]
135//! static mut MODULE_DEF: PyModuleDef = PyModuleDef {
136//!     m_base: PyModuleDef_HEAD_INIT,
137//!     m_name: c"string_sum".as_ptr(),
138//!     m_doc: c"A Python module written in Rust.".as_ptr(),
139//!     m_size: 0,
140//!     m_methods: (&raw mut METHODS).cast(),
141//!     m_slots: (&raw mut SLOTS).cast(),
142//!     m_traverse: None,
143//!     m_clear: None,
144//!     m_free: None,
145//! };
146//!
147//! static mut METHODS: [PyMethodDef; 2] = [
148//!     PyMethodDef {
149//!         ml_name: c"sum_as_string".as_ptr(),
150//!         ml_meth: PyMethodDefPointer {
151//!             PyCFunctionFast: sum_as_string,
152//!         },
153//!         ml_flags: METH_FASTCALL,
154//!         ml_doc: c"returns the sum of two integers as a string".as_ptr(),
155//!     },
156//!     // A zeroed PyMethodDef to mark the end of the array.
157//!     PyMethodDef::zeroed(),
158//! ];
159//!
160//! #[cfg(Py_3_15)]
161//! PyABIInfo_VAR!(ABI_INFO);
162//!
163//! const SLOTS_LEN: usize =
164//!     1 + cfg!(Py_3_12) as usize + cfg!(Py_GIL_DISABLED) as usize + 4 * (cfg!(Py_3_15) as usize);
165//! static mut SLOTS: [PyModuleDef_Slot; SLOTS_LEN] = [
166//!     #[cfg(Py_3_15)]
167//!     PyModuleDef_Slot {
168//!         slot: Py_mod_abi,
169//!         value: (&raw mut ABI_INFO).cast(),
170//!     },
171//!     #[cfg(Py_3_15)]
172//!     PyModuleDef_Slot {
173//!         slot: Py_mod_name,
174//!         // safety: Python does not write to this field
175//!         value: c"string_sum".as_ptr() as *mut c_void,
176//!     },
177//!     #[cfg(Py_3_15)]
178//!     PyModuleDef_Slot {
179//!         slot: Py_mod_doc,
180//!         // safety: Python does not write to this field
181//!         value: c"A Python module written in Rust.".as_ptr() as *mut c_void,
182//!     },
183//!     #[cfg(Py_3_15)]
184//!     PyModuleDef_Slot {
185//!         slot: Py_mod_methods,
186//!         value: (&raw mut METHODS).cast(),
187//!     },
188//!     #[cfg(Py_3_12)]
189//!     PyModuleDef_Slot {
190//!         slot: Py_mod_multiple_interpreters,
191//!         value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED,
192//!     },
193//!     #[cfg(Py_GIL_DISABLED)]
194//!     PyModuleDef_Slot {
195//!         slot: Py_mod_gil,
196//!         value: Py_MOD_GIL_NOT_USED,
197//!     },
198//!     PyModuleDef_Slot {
199//!         slot: 0,
200//!         value: ptr::null_mut(),
201//!     },
202//! ];
203//!
204//! // The module initialization function
205//! #[cfg(not(Py_3_15))]
206//! #[allow(non_snake_case, reason = "must be named `PyInit_<your_module>`")]
207//! #[no_mangle]
208//! pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
209//!     PyModuleDef_Init(&raw mut MODULE_DEF)
210//! }
211//!
212//! #[cfg(Py_3_15)]
213//! #[allow(non_snake_case, reason = "must be named `PyModExport_<your_module>`")]
214//! #[no_mangle]
215//! pub unsafe extern "C" fn PyModExport_string_sum() -> *mut PyModuleDef_Slot {
216//!     (&raw mut SLOTS).cast()
217//! }
218//!
219//! /// A helper to parse function arguments
220//! /// If we used PyForge's proc macros they'd handle all of this boilerplate for us :)
221//! unsafe fn parse_arg_as_i32(obj: *mut PyObject, n_arg: usize) -> Option<i32> {
222//!     if PyLong_Check(obj) == 0 {
223//!         let msg = format!(
224//!             "sum_as_string expected an int for positional argument {}\0",
225//!             n_arg
226//!         );
227//!         PyErr_SetString(PyExc_TypeError, msg.as_ptr().cast::<c_char>());
228//!         return None;
229//!     }
230//!
231//!     // Let's keep the behaviour consistent on platforms where `c_long` is bigger than 32 bits.
232//!     // In particular, it is an i32 on Windows but i64 on most Linux systems
233//!     let mut overflow = 0;
234//!     let i_long: c_long = PyLong_AsLongAndOverflow(obj, &mut overflow);
235//!
236//!     #[allow(
237//!         irrefutable_let_patterns,
238//!         reason = "some platforms have c_long equal to i32"
239//!     )]
240//!     if overflow != 0 {
241//!         raise_overflowerror(obj);
242//!         None
243//!     } else if let Ok(i) = i_long.try_into() {
244//!         Some(i)
245//!     } else {
246//!         raise_overflowerror(obj);
247//!         None
248//!     }
249//! }
250//!
251//! unsafe fn raise_overflowerror(obj: *mut PyObject) {
252//!     let obj_repr = PyObject_Str(obj);
253//!     if !obj_repr.is_null() {
254//!         let mut size = 0;
255//!         let p = PyUnicode_AsUTF8AndSize(obj_repr, &mut size);
256//!         if !p.is_null() {
257//!             let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
258//!                 p.cast::<u8>(),
259//!                 size as usize,
260//!             ));
261//!             let msg = format!("cannot fit {} in 32 bits\0", s);
262//!
263//!             PyErr_SetString(PyExc_OverflowError, msg.as_ptr().cast::<c_char>());
264//!         }
265//!         Py_DECREF(obj_repr);
266//!     }
267//! }
268//!
269//! pub unsafe extern "C" fn sum_as_string(
270//!     _self: *mut PyObject,
271//!     args: *mut *mut PyObject,
272//!     nargs: Py_ssize_t,
273//! ) -> *mut PyObject {
274//!     if nargs != 2 {
275//!         PyErr_SetString(
276//!             PyExc_TypeError,
277//!             c"sum_as_string expected 2 positional arguments".as_ptr(),
278//!         );
279//!         return std::ptr::null_mut();
280//!     }
281//!
282//!     let (first, second) = (*args, *args.add(1));
283//!
284//!     let first = match parse_arg_as_i32(first, 1) {
285//!         Some(x) => x,
286//!         None => return std::ptr::null_mut(),
287//!     };
288//!     let second = match parse_arg_as_i32(second, 2) {
289//!         Some(x) => x,
290//!         None => return std::ptr::null_mut(),
291//!     };
292//!
293//!     match first.checked_add(second) {
294//!         Some(sum) => {
295//!             let string = sum.to_string();
296//!             PyUnicode_FromStringAndSize(string.as_ptr().cast::<c_char>(), string.len() as isize)
297//!         }
298//!         None => {
299//!             PyErr_SetString(PyExc_OverflowError, c"arguments too large to add".as_ptr());
300//!             std::ptr::null_mut()
301//!         }
302//!     }
303//! }
304//! ```
305//!
306//! With those two files in place, now `maturin` needs to be installed. This can be done using
307//! Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin`
308//! into it:
309//! ```bash
310//! $ cd string_sum
311//! $ python -m venv .env
312//! $ source .env/bin/activate
313//! $ pip install maturin
314//! ```
315//!
316//! Now build and execute the module:
317//! ```bash
318//! $ maturin develop
319//! # lots of progress output as maturin runs the compilation...
320//! $ python
321//! >>> import string_sum
322//! >>> string_sum.sum_as_string(5, 20)
323//! '25'
324//! ```
325//!
326//! As well as with `maturin`, it is possible to build using [setuptools-rust] or
327//! [manually][manual_builds]. Both offer more flexibility than `maturin` but require further
328//! configuration.
329//!
330//! This example stores the module definition statically and uses the `PyModule_Create` function
331//! in the CPython C API to register the module. This is the "old" style for registering modules
332//! and has the limitation that it cannot support subinterpreters. You can also create a module
333//! using the new multi-phase initialization API that does support subinterpreters. See the
334//! `sequential` project located in the `examples` directory at the root of the `pyforge-ffi` crate
335//! for a worked example of how to this using `pyforge-ffi`.
336//!
337//! # Using Python from Rust
338//!
339//! To embed Python into a Rust binary, you need to ensure that your Python installation contains a
340//! shared library. The following steps demonstrate how to ensure this (for Ubuntu).
341//!
342//! To install the Python shared library on Ubuntu:
343//! ```bash
344//! sudo apt install python3-dev
345//! ```
346//!
347//! While most projects use the safe wrapper provided by pyo3,
348//! you can take a look at the [`orjson`] library as an example on how to use `pyforge-ffi` directly.
349//! For those well versed in C and Rust the [tutorials] from the CPython documentation
350//! can be easily converted to rust as well.
351//!
352//! [tutorials]: https://docs.python.org/3/extending/
353//! [`orjson`]: https://github.com/ijl/orjson
354//! [capi]: https://docs.python.org/3/c-api/index.html
355//! [`maturin`]: https://github.com/PyForge/maturin "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages"
356//! [`pyforge-build-config`]: https://docs.rs/pyforge-build-config
357//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book"
358#![doc = concat!("[manual_builds]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution.html#manual-builds \"Manual builds - Building and Distribution - PyForge user guide\"")]
359//! [setuptools-rust]: https://github.com/PyForge/setuptools-rust "Setuptools plugin for Rust extensions"
360//! [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI"
361#![doc = concat!("[Features chapter of the guide]: https://github.com/abdulwahed-sweden/pyforge/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features reference - PyForge user guide\"")]
362#![allow(
363    missing_docs,
364    non_camel_case_types,
365    non_snake_case,
366    non_upper_case_globals,
367    clippy::upper_case_acronyms,
368    clippy::missing_safety_doc,
369    clippy::ptr_eq
370)]
371#![warn(elided_lifetimes_in_paths, unused_lifetimes)]
372// This crate is a hand-maintained translation of CPython's headers, so requiring "unsafe"
373// blocks within those translations increases maintenance burden without providing any
374// additional safety. The safety of the functions in this crate is determined by the
375// original CPython headers
376#![allow(unsafe_op_in_unsafe_fn)]
377
378// Until `extern type` is stabilized, use the recommended approach to
379// model opaque types:
380// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
381macro_rules! opaque_struct {
382    ($(#[$attrs:meta])* $pub:vis $name:ident) => {
383        $(#[$attrs])*
384        #[repr(C)]
385        $pub struct $name([u8; 0]);
386    };
387}
388
389/// This is a helper macro to create a `&'static CStr`.
390///
391/// It can be used on all Rust versions supported by PyForge, unlike c"" literals which
392/// were stabilised in Rust 1.77.
393///
394/// Due to the nature of PyForge making heavy use of C FFI interop with Python, it is
395/// common for PyForge to use CStr.
396///
397/// Examples:
398///
399/// ```rust,no_run
400/// use std::ffi::CStr;
401///
402/// const HELLO: &CStr = pyo3_ffi::c_str!("hello");
403/// static WORLD: &CStr = pyo3_ffi::c_str!("world");
404/// ```
405#[macro_export]
406macro_rules! c_str {
407    // TODO: deprecate this now MSRV is above 1.77
408    ($s:expr) => {
409        $crate::_cstr_from_utf8_with_nul_checked(concat!($s, "\0"))
410    };
411}
412
413/// Private helper for `c_str!` macro.
414#[doc(hidden)]
415pub const fn _cstr_from_utf8_with_nul_checked(s: &str) -> &std::ffi::CStr {
416    match std::ffi::CStr::from_bytes_with_nul(s.as_bytes()) {
417        Ok(cstr) => cstr,
418        Err(_) => panic!("string contains nul bytes"),
419    }
420}
421
422// Macros for declaring `extern` blocks that link against libpython.
423// See `impl_/macros.rs` for the implementation.
424include!("impl_/macros.rs");
425
426pub mod compat;
427mod impl_;
428
429pub use self::abstract_::*;
430pub use self::bltinmodule::*;
431pub use self::boolobject::*;
432pub use self::bytearrayobject::*;
433pub use self::bytesobject::*;
434pub use self::ceval::*;
435pub use self::codecs::*;
436pub use self::compile::*;
437pub use self::complexobject::*;
438#[cfg(not(Py_LIMITED_API))]
439pub use self::context::*;
440#[cfg(not(Py_LIMITED_API))]
441pub use self::datetime::*;
442pub use self::descrobject::*;
443pub use self::dictobject::*;
444pub use self::enumobject::*;
445pub use self::fileobject::*;
446pub use self::fileutils::*;
447pub use self::floatobject::*;
448#[cfg(Py_3_9)]
449pub use self::genericaliasobject::*;
450pub use self::import::*;
451pub use self::intrcheck::*;
452pub use self::iterobject::*;
453pub use self::listobject::*;
454pub use self::longobject::*;
455#[cfg(not(Py_LIMITED_API))]
456pub use self::marshal::*;
457pub use self::memoryobject::*;
458pub use self::methodobject::*;
459pub use self::modsupport::*;
460pub use self::moduleobject::*;
461pub use self::object::*;
462pub use self::objimpl::*;
463pub use self::osmodule::*;
464#[cfg(not(any(Py_LIMITED_API, Py_3_10)))]
465pub use self::pyarena::*;
466#[cfg(Py_3_11)]
467pub use self::pybuffer::*;
468pub use self::pycapsule::*;
469pub use self::pyerrors::*;
470pub use self::pyframe::*;
471pub use self::pyhash::*;
472pub use self::pylifecycle::*;
473pub use self::pymem::*;
474pub use self::pyport::*;
475pub use self::pystate::*;
476pub use self::pystrtod::*;
477pub use self::pythonrun::*;
478pub use self::pytypedefs::*;
479pub use self::rangeobject::*;
480pub use self::refcount::*;
481pub use self::setobject::*;
482pub use self::sliceobject::*;
483pub use self::structseq::*;
484pub use self::sysmodule::*;
485pub use self::traceback::*;
486pub use self::tupleobject::*;
487pub use self::typeslots::*;
488pub use self::unicodeobject::*;
489pub use self::warnings::*;
490pub use self::weakrefobject::*;
491
492mod abstract_;
493// skipped asdl.h
494// skipped ast.h
495mod bltinmodule;
496mod boolobject;
497mod bytearrayobject;
498mod bytesobject;
499// skipped cellobject.h
500mod ceval;
501// skipped classobject.h
502mod codecs;
503mod compile;
504mod complexobject;
505#[cfg(not(Py_LIMITED_API))]
506mod context;
507#[cfg(not(Py_LIMITED_API))]
508pub(crate) mod datetime;
509mod descrobject;
510mod dictobject;
511// skipped dynamic_annotations.h
512mod enumobject;
513// skipped errcode.h
514// skipped exports.h
515mod fileobject;
516mod fileutils;
517mod floatobject;
518// skipped empty frameobject.h
519mod genericaliasobject;
520mod import;
521// skipped interpreteridobject.h
522mod intrcheck;
523mod iterobject;
524mod listobject;
525// skipped longintrepr.h
526mod longobject;
527#[cfg(not(Py_LIMITED_API))]
528pub mod marshal;
529mod memoryobject;
530mod methodobject;
531mod modsupport;
532mod moduleobject;
533// skipped namespaceobject.h
534mod object;
535mod objimpl;
536// skipped odictobject.h
537// skipped opcode.h
538// skipped osdefs.h
539mod osmodule;
540// skipped parser_interface.h
541// skipped patchlevel.h
542// skipped picklebufobject.h
543// skipped pyctype.h
544// skipped py_curses.h
545#[cfg(not(any(Py_LIMITED_API, Py_3_10)))]
546mod pyarena;
547#[cfg(Py_3_11)]
548mod pybuffer;
549mod pycapsule;
550// skipped pydtrace.h
551mod pyerrors;
552// skipped pyexpat.h
553// skipped pyfpe.h
554mod pyframe;
555mod pyhash;
556mod pylifecycle;
557// skipped pymacconfig.h
558// skipped pymacro.h
559// skipped pymath.h
560mod pymem;
561mod pyport;
562mod pystate;
563// skipped pystats.h
564mod pythonrun;
565// skipped pystrhex.h
566// skipped pystrcmp.h
567mod pystrtod;
568// skipped pythread.h
569// skipped pytime.h
570mod pytypedefs;
571mod rangeobject;
572mod refcount;
573mod setobject;
574mod sliceobject;
575mod structseq;
576mod sysmodule;
577mod traceback;
578// skipped tracemalloc.h
579mod tupleobject;
580mod typeslots;
581mod unicodeobject;
582mod warnings;
583mod weakrefobject;
584
585// Additional headers that are not exported by Python.h
586#[deprecated(note = "Python 3.12")]
587pub mod structmember;
588
589// "Limited API" definitions matching Python's `include/cpython` directory.
590#[cfg(not(Py_LIMITED_API))]
591mod cpython;
592
593#[cfg(not(Py_LIMITED_API))]
594pub use self::cpython::*;