Skip to content

Add module state support - #6245

Draft
MatthieuDartiailh wants to merge 24 commits into
PyO3:mainfrom
MatthieuDartiailh:module-state-support
Draft

Add module state support#6245
MatthieuDartiailh wants to merge 24 commits into
PyO3:mainfrom
MatthieuDartiailh:module-state-support

Conversation

@MatthieuDartiailh

@MatthieuDartiailh MatthieuDartiailh commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

This builds upon #5600 but takes a slightly different approach to managing and accessing the module state:

  • the module state type is passed to pymodule using a new state kwargs or declared inside a mod using #[pymodule_state]
  • the module state is initialized by either:
    • the function on which pymodule is applied
    • the #[pymodule_init] function inside a mod decorated with #[pymodule]
  • module traverse and clear can only be declared when using a mod (and use #[pymodule_traverse] and #[pymodule_clear]

As a consequence there is single module state and there is no need for a type map to store it a Box<dyn Any> is sufficient.

The state can be accessed either through a module object using module_state or module_state_mut which is unsafe (since we cannot guarantee the unicity), locking the module using a critical sction can make it safe, however in practice I would expect Mutex to be used on the state to provide a finer granularity.

It can also be accessed from a type object using module_state (no mutable variant since the user does not get the module to lock) or from the Python token through type_module_state which takes 2 type parameters (one describes the type, one the state).

For this to work the type needs to be created using PyType_FromModuleAndSpec. For this I added add_class_with_module to the Module object, which will properly create the class and error if the class was already created. (If required I could probably split this into a separate PR)

An example is included (compiles and passes tests).

This is draft because:

  • I need to add dedicated tests

bazaah and others added 23 commits July 26, 2026 12:06
This commit adds the core ModuleState & StateCapsule structs for
interop with python's ModuleDef m_size field.

We choose to keep the in-interpreter size small, only being a NonNull
ptr to the actual (allocated by Rust) state. This is suboptimal from
a pointer chasing perspective, because we'll eventually have
interpreter->StateCapsule->TypeMap<T> dereference chains, but it makes
it easier to implement and reason about.

(cherry picked from commit 33e8324)
This commit sets ModuleDef.m_size to size_of::<ModuleState>, and adds
the relevant callback to ModuleDef.m_free.
Asserting we initialize ModuleState correctly, that it is accessible
from both module_exec calls -- that is, during module initialization --
and from after the module is loaded.

(cherry picked from commit fabaf73)
This is ported from some of my internal code, with adjustments for
edition = 2024.

It was originally forked from rust-typemap but has diverged
significantly, and been updated for newer rust versions.

I freely relicense it under PyO3's licensing terms.

(cherry picked from commit 4f0c205)
Within we switch over to the real typemap backed StateMap
implementation.

We also add a marker trait, ModuleStateType which guarantees the
properties we expect from stored StateMap types.

Namely: Clone + Send (not Sync), though I'm not sure these are the
right guarantees for python's memory model.

Send is a requirement, as python can move PyModules between threads at
will, and Clone makes dealing with ModuleState much easier longer term
(speaking from experience with typemaps).

I'm not sure if Sync should be a requirement too, as it's not clear to
me if PyModules can be concurrently accessed between attached python
thread states. I'm worried the answer might change between free-threaded
and "normal" python builds.

(cherry picked from commit 1de1b3f)
These commit adds three methods to the external interface for PyModules,
exposing a limited ability to interact with the per-module state area.

- state_ref
- state_mut
- state_or_init

We take a hands-off approach to what the concrete "shape" of this state
is, beyond requiring it is compatible with the marker ModuleStateType
trait.

Modules may store as many types as needed, and all the normal rust
visibility conventions apply, allowing them to guard type invariants, or
prevent external access entirely.

(cherry picked from commit 2e7b1f2)
(cherry picked from commit 5987894)
… with pymodule

The module state is allocated only if a module state is requested, module init function must return a state instance.
… footgunny impl from PyAny

Also add the machinery to allow attaching a module to a type

This is required to be able to access the module state from the type at low cost
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants