Files
RustPython/examples/call_between_rust_and_python.rs
Jeong, YunWon 100dac0bbe Reject payload receivers on #[pymethod] and #[pygetset] (#8898)
* Report errors from #[pyclass] trait items

The trait branch of impl_pyclass_impl built its own ImplContext, so the
errors collected while extracting trait items were never emitted: an
invalid #[pymethod] or #[pygetset] in a #[pyclass] trait was left out of
the class without a compile error. The trait branch now uses the shared
context whose errors are emitted.

Assisted-by: Claude Code:claude-opus-5-5

* Pass the object to #[pymethod] and #[pygetset] receivers outside vm

Outside crates/vm, where impl Py<T> cannot be written, #[pymethod] and
#[pygetset] receivers that took &self become zelf: &Py<Self> inside the
existing impl T blocks. Where Rust code calls one of these methods on a
payload value, the logic stays in a payload method and the Python method
forwards to it.

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Grok:grok-4.7

* Reject payload receivers on #[pymethod] and #[pygetset]

A #[pymethod] or #[pygetset] item with a self receiver is now a compile
error when self is the payload: in an impl for T rather than Py<T> or
PyRef<T>, and in a #[pyclass] trait. The error points to taking
zelf: &Py<Self> or zelf: PyRef<Self>, or defining the item in impl Py<T>.

Assisted-by: Claude Code:claude-opus-5-5

* Pass the object to the layout test getter

The subclass layout test's extra getter is a #[pygetset] on the
payload. Take zelf: &Py<Self> so the receiver is the object.

Assisted-by: Grok:4.7
2026-09-30 22:12:16 +09:00

91 lines
2.5 KiB
Rust

use rustpython::InterpreterBuilderExt;
use rustpython::vm::{
Py, PyObject, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine, pyclass, pymodule,
};
pub fn main() {
let builder = rustpython::Interpreter::builder(Default::default());
let def = rust_py_module::module_def(&builder.ctx);
let interp = builder.init_stdlib().add_native_module(def).build();
interp.enter(|vm| {
vm.insert_sys_path(vm.new_pyobj("examples"))
.expect("add path");
let module = vm.import("call_between_rust_and_python", 0).unwrap();
let init_fn = module.get_attr("python_callback", vm).unwrap();
init_fn.call((), vm).unwrap();
let take_string_fn = module.get_attr("take_string", vm).unwrap();
take_string_fn
.call((String::from("Rust string sent to python"),), vm)
.unwrap();
})
}
#[pymodule]
mod rust_py_module {
use super::*;
use rustpython::vm::{PyObjectRef, convert::ToPyObject};
#[pyfunction]
fn rust_function(
num: i32,
s: String,
python_person: PythonPerson,
_vm: &VirtualMachine,
) -> RustStruct {
println!(
"Calling standalone rust function from python passing args:
num: {},
string: {},
python_person.name: {}",
num, s, python_person.name
);
RustStruct {
numbers: NumVec(vec![1, 2, 3, 4]),
}
}
#[derive(Debug, Clone)]
struct NumVec(Vec<i32>);
impl ToPyObject for NumVec {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
let list = self.0.into_iter().map(|e| vm.new_pyobj(e)).collect();
vm.ctx.new_list(list).to_pyobject(vm)
}
}
#[pyattr]
#[pyclass(module = "rust_py_module", name = "RustStruct")]
#[derive(Debug, PyPayload)]
struct RustStruct {
numbers: NumVec,
}
#[pyclass]
impl RustStruct {
#[pygetset]
fn numbers(zelf: &Py<Self>) -> NumVec {
zelf.numbers.clone()
}
#[pymethod]
fn print_in_rust_from_python(_zelf: &Py<Self>) {
println!("Calling a rust method from python");
}
}
struct PythonPerson {
name: String,
}
impl<'a> TryFromBorrowedObject<'a> for PythonPerson {
fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
let name = obj.get_attr("name", vm)?.try_into_value::<String>(vm)?;
Ok(Self { name })
}
}
}