Sharing Rust structs in foreign libraries / Writing complex wrapper classes #3634
-
I apologize if this has been answered somewhere already, but I've tried to understand the documentation and code search GitHub, and can't really find a solution that seems to apply for my issue. There seems to be a few related discussions which I've mentioned below. I also apologize as I'm pretty unfamiliar with Rust and this question might actually be super easy. Most examples in the docs focus on rust structs that can be annotated and have primitive members. I am attempting to wrap an external crate, with various nested structs and containers. I've created a minimal example at /~https://github.com/warriorstar-orion/pyo3_complex_wrapper. You can see the "foreign" structs are: pub struct Foo {
pub mapping: HashMap<String, Vec<Bar>>,
}
pub struct Bar {
pub num: i32,
pub word: String,
} and my wrapper pyclasses are #[pyclass(module = "pyo3_complex_wrapper")]
#[pyo3(name = "Foo")]
pub struct FooWrapper(foreign::Foo);
#[pyclass(module = "pyo3_complex_wrapper")]
#[pyo3(name = "Bar")]
pub struct BarWrapper(foreign::Bar);
>>> from pyo3_complex_wrapper import Foo
>>> foo = Foo()
>>> print(foo.compute())
hellohello
therethere
worldworldworld
>>> bars = foo.pop("greeting") # obvious ownership change
>>> bars
[<Bar 2, 'hello'>, <Bar 2, 'there'>]
>>> print(foo.compute())
worldworldworld
>>> bars[0].word = 'goodbye'
[<Bar 2, 'goodbye'>, <Bar 2, 'there'>] The issue arises when I attempt to implement something like this (ignore how terrible the indirection is): >>> from pyo3_complex_wrapper import Foo
>>> foo = Foo()
>>> bar = foo["greeting"][0]
>>> bar.word = "goodbye"
>>> print(foo.compute())
goodbyegoodbye
therethere
worldworld My understanding is that by assigning a bar to a python local, we're crossing this boundary where the native Bar struct needs to be owned by both the I've attempted several solutions:
#[pyclass]
struct FooWrapper {
foo: foreign::Foo,
bars: PyDict<PyList<BarWrapper>>,
}
#[pymethods]
impl FooWrapper {
#[new]
fn new() -> Self {
// Remove all the elements from foo.mapping so they can be owned by the wrapper/pylist/pydict
}
fn compute() -> String {
// We gotta put all the objects back into foo.mapping so that the foreign struct can do the work
}
} Again, sorry if one of the above solutions applies and it's just a matter of implementation; the documentation might benefit from a section specifically on "wrapper" libraries with a fleshed out example similar to this minimal case, or I just may need to learn more Rust before tackling this. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
Those structures do not support shared ownership (not even in Rust) and hence you cannot use that on language level. You would would have to introduce something like If you really cannot modify these structures because they are defined by a foreign crate, I would suggest to use shared ownership only conceptually but not actually, for example do not store struct BarWrapper {
foo: Py<FooWrapper>,
key: String,
index: usize,
}
impl BarWrapper {
fn value(&self, py: Python<'_>) -> &Bar {
&self.foo.borrow(py).0[&self.key][self.index]
}
} (I have not compiled this, so it will most likely trigger some compiler errors, but I think it can be made to work.) |
Beta Was this translation helpful? Give feedback.
Those structures do not support shared ownership (not even in Rust) and hence you cannot use that on language level. You would would have to introduce something like
Arc
or our ownPy
smart pointer into the mix so that you would store e.g.HashMap<String, Vec<Arc<Bar>>
orHashMap<String, Vec<Py<Bar>>
.If you really cannot modify these structures because they are defined by a foreign crate, I would suggest to use shared ownership only conceptually but not actually, for example do not store
Bar
withinBarWrapper
, but the necessary data to access the bar, e.g.