Skip to content

Allow creating PyCapsule objects#7595

Merged
youknowone merged 1 commit intoRustPython:mainfrom
bschoenmaeckers:pycapsule
Apr 14, 2026
Merged

Allow creating PyCapsule objects#7595
youknowone merged 1 commit intoRustPython:mainfrom
bschoenmaeckers:pycapsule

Conversation

@bschoenmaeckers
Copy link
Copy Markdown
Contributor

@bschoenmaeckers bschoenmaeckers commented Apr 13, 2026

Allow creation of PyCapsule objects. The destructor is a C function for easy interop with other languages.

Summary by CodeRabbit

Release Notes

  • New Features
    • Capsule objects now support storing opaque pointers with optional destructor callbacks for proper resource cleanup
    • Added new APIs to create capsules and retrieve stored pointer data

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

Modified the PyCapsule struct to store an opaque pointer and optional C-level destructor callback instead of remaining empty. Added public constructor and accessor methods to PyCapsule, extended it with Destructor trait support, and introduced a helper method Context::new_capsule() for creating capsule instances.

Changes

Cohort / File(s) Summary
Capsule Implementation
crates/vm/src/builtins/capsule.rs
Changed PyCapsule from a zero-sized struct to store AtomicPtr<c_void> and an optional unsafe extern "C" destructor. Added new() constructor, pointer() accessor, removed Clone/Copy derives, and implemented Destructor trait for cleanup on deletion.
Context Helper
crates/vm/src/vm/context.rs
Added new_capsule() method to Context that constructs and binds a PyCapsule instance from a raw pointer and optional C destructor callback.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A capsule is born with a pointer so true,
A destructor assigned when its work is through,
From context we craft it, in memory it sleeps,
A guardian of pointers, the secrets it keeps! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Allow creating PyCapsule objects' directly and clearly summarizes the main change: adding the ability to create PyCapsule objects with new constructor methods and capsule support.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/builtins/capsule.rs`:
- Around line 28-35: The constructor Capsule::new currently accepts a raw ptr
and stores it without validation; update it to reject NULL pointers by
validating ptr before constructing—either change the signature to return
Result<Self, ErrorType> (e.g., Result<Capsule, InvalidPointerError>) and return
an Err when ptr.is_null(), or perform a defensive check and panic with a clear
message; ensure the check happens before setting self.ptr and mention
PyCapsule_New and PyCapsule_IsValid semantics in the error or panic so callers
know NULL is not allowed, and keep the destructor handling unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 89365b54-c937-4160-99bf-fe663f7677a7

📥 Commits

Reviewing files that changed from the base of the PR and between 7e637e8 and dcecdf9.

📒 Files selected for processing (2)
  • crates/vm/src/builtins/capsule.rs
  • crates/vm/src/vm/context.rs

Comment on lines +28 to +35
pub fn new(
ptr: *mut c_void,
destructor: Option<unsafe extern "C" fn(_: *mut PyObject)>,
) -> Self {
Self {
ptr: ptr.into(),
destructor,
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the capsule.rs file
find . -name "capsule.rs" -type f

Repository: RustPython/RustPython

Length of output: 100


🏁 Script executed:

# Once found, read the file to see the constructor at lines 28-35
if [ -f "crates/vm/src/builtins/capsule.rs" ]; then
  cat -n "crates/vm/src/builtins/capsule.rs" | head -50
fi

Repository: RustPython/RustPython

Length of output: 1782


🏁 Script executed:

# Check the entire capsule.rs file to understand the implementation
if [ -f "crates/vm/src/builtins/capsule.rs" ]; then
  wc -l "crates/vm/src/builtins/capsule.rs"
fi

Repository: RustPython/RustPython

Length of output: 101


🏁 Script executed:

# Search for any null pointer validation in the capsule implementation
rg -i "null|is_null" crates/vm/src/builtins/capsule.rs -A 3 -B 3

Repository: RustPython/RustPython

Length of output: 47


🌐 Web query:

Python C API PyCapsule_New null pointer requirement

💡 Result:

Yes, the Python C API function PyCapsule_New explicitly requires that the pointer argument must not be NULL. Passing a NULL pointer will raise a ValueError: "PyCapsule_New called with null pointer". From the official documentation: PyObject* PyCapsule_New(void *pointer, const char *name, PyCapsule_Destructor destructor) "Create a PyCapsule encapsulating the pointer. The pointer argument may not be NULL. On failure, set an exception and return NULL." This requirement is consistent across Python versions, including the latest (3.14.3) and earlier stable versions like 3.11. A valid capsule must have a non-NULL pointer, as stated in PyCapsule_IsValid: "A valid capsule is non-NULL, passes PyCapsule_CheckExact, has a non-NULL pointer stored in it, and its internal name matches the name parameter." Errors like "PyCapsule_New called with null pointer" occur when libraries (e.g., matplotlib on Windows) inadvertently pass NULL, which is a bug in the caller, not the API itself. Users should ensure the pointer is valid before calling PyCapsule_New and check the return value for NULL (due to memory allocation failure).

Citations:


Validate that the capsule pointer is non-NULL before constructing the object.

The Python C API explicitly forbids NULL pointers in PyCapsule_New() and raises a ValueError if one is passed. Additionally, PyCapsule_IsValid() requires a non-NULL pointer as part of its validity check. This constructor currently accepts null pointers without validation, allowing callers to construct capsules that violate CPython semantics. The non-fallible signature prevents proper error handling; consider returning Result<Self, ...> or validating the pointer before storing it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/capsule.rs` around lines 28 - 35, The constructor
Capsule::new currently accepts a raw ptr and stores it without validation;
update it to reject NULL pointers by validating ptr before constructing—either
change the signature to return Result<Self, ErrorType> (e.g., Result<Capsule,
InvalidPointerError>) and return an Err when ptr.is_null(), or perform a
defensive check and panic with a clear message; ensure the check happens before
setting self.ptr and mention PyCapsule_New and PyCapsule_IsValid semantics in
the error or panic so callers know NULL is not allowed, and keep the destructor
handling unchanged.

Copy link
Copy Markdown
Contributor

@ShaharNaveh ShaharNaveh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

welcome to the project:)
can you please share some information about this change? what is it trying to achieve?

self.ptr.load(core::sync::atomic::Ordering::Relaxed)
}

fn destructor(&self) -> Option<unsafe extern "C" fn(_: *mut PyObject)> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does

Suggested change
fn destructor(&self) -> Option<unsafe extern "C" fn(_: *mut PyObject)> {
const fn destructor(&self) -> Option<unsafe extern "C" fn(_: *mut PyObject)> {

compile?

@bschoenmaeckers
Copy link
Copy Markdown
Contributor Author

welcome to the project:)
can you please share some information about this change? what is it trying to achieve?

I needed this while experimenting with a capi at #7562. But this might be useful for other usecases as well.

@ShaharNaveh ShaharNaveh requested a review from youknowone April 13, 2026 22:01
Copy link
Copy Markdown
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, Thank you!

@youknowone youknowone merged commit 7544628 into RustPython:main Apr 14, 2026
20 checks passed
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.

3 participants