Repository navigation
Conversation
Signed-off-by: Ikeh Chukwuka <mrikehchukwuka@gmail.com>
Signed-off-by: Ikeh Chukwuka <mrikehchukwuka@gmail.com>
…ex<I>` tag type Signed-off-by: Ikeh Chukwuka <mrikehchukwuka@gmail.com>
to_array and get
thunderseethe
left a comment
There was a problem hiding this comment.
This is a great start! Thanks for the submission.
after studying forward_list and the use of &mut for c++ move, i was curious to ask if there have been any discussion about offering rust style move (by value)? i understand this would be a mismatch between both languages, but i'm curious for case when the moved-from c++ object itself needs not to be used after it is moved. because i initially implemented move to take ownership of the other object.
I think this is definitely one of the more interesting questions about FFI between C++ and Rust. I would love to hear how you would tackle some of the problems for array. Especially around how to move items in and out of the array in both languages. Where Rust expects to be able to destructively move things out of an array that it owns, callers have to be careful not to leave an array element in an invalid state where Rust expects it won't be referenced again but C++ attemps to read it expecting the element to be left in a moved-from state.
I left one minor comment on section 1, but overall looks good. Can you show me an example of some failing overloads for array (per section 2)?
| /// C++ also has a `const T&& get(const std::array&&)` overload but Rust types can't be `const`, | ||
| /// so a constant rvalue array has the same Rust type as a moved array. That overload | ||
| /// can't be represented separately from this one. | ||
| fn get(array: StdArray3, _index: Index<0>) -> T { |
There was a problem hiding this comment.
For methods that take an array "by value" like this, how can we uphold the lifetime guarentees here? This won't really matter for this case where T is an int but what if T was an object with a destructor. How can we ensure that when we return T by value that T won't get dropped when we drop the array initially holding it. If we can't, can we document the invariants required of our array when we call this method?
There was a problem hiding this comment.
true for int, nothing is ever destroyed. but once T has a destructor, copying the element's bytes out the way ptr::read would breaks, because std::array's destructor always destroys every element and has no way to skip the one we took. i reproduced this with std::array<std::string, 3> and got a double free when the copied string was dropped after the array was deleted. the version that holds up runs T's move constructor in c++, keeps the new T on the c++ heap behind a pointer, and then deletes the array exactly once, which destroys the moved-from shell harmlessly.
thank you, i will update the by-value get's docs to account for these invariants for a generic T, and i'll keep documenting as my findings surfaces more.
|
thank you for the review! after taking some time to think it through, the rule i'd build on is that rust only ever moves pointers to c++ objects and not their bytes. which the array wrapper already does. moving a for moving the whole array, the for moving an element out, rust's own rules line up with c++ more than i expected. safe rust won't let you move out of a slot you only borrow, so you need struct ValueArray {
ptr: *mut c_void,
note: Note, // a rust field with its own `Drop`
}
impl Drop for ValueArray {
fn drop(&mut self) {
let p = self.ptr;
unsafe { cpp!([p as "std::array<Value, 3>*"] { delete p; }) }
}
}
fn get_0_forget_self(self) -> ValueBox {
let slf = self.ptr;
std::mem::forget(self); // skips `Drop` for `ValueArray` and the drop of `note`
let out = unsafe {
cpp!([slf as "std::array<Value, 3>*"] -> *mut c_void as "Value*" {
Value* out = new Value(std::get<0>(std::move(*slf)));
delete slf;
return out;
})
};
ValueBox(out)
}in this first example, forgetting fn get_0_move(self) -> ValueBox {
let slf = self.ptr;
let out = unsafe {
cpp!([slf as "std::array<Value, 3>*"] -> *mut c_void as "Value*" {
return new Value(std::get<0>(std::move(*slf))); // slot 0 now holds a moved-from object
})
};
ValueBox(out) // its `Drop` deletes the new value once
// `self` drops here, and its `Drop` deletes the array, the moved-from object included
}for moving elements in, the way i see the split, the c++ glue owns the move-then-destroy steps and any compile-time checks, and the rust types own the promise that rust never relocates a c++ object's bytes or drops one twice. i believe that is roughly how cxx divides it with its phew, i'll start working on step 2, plus the doc updates. thank you once again. |
this pr adds rust wrappers for
std::array<int, 3>overloads covering three method names, the constructors,to_arrayandget.std::arrayconstructors are aggregate initialization with 0 toNinitializers, in addition with the copy and move constructors. all of them are implemented forN = 3, so the constructor set is complete. unlikeforward_list, none of its overloads clash becausestd::arrayhas nocountorvalueconstructors.c++ has two zero-argument forms (
a;anda{};), but only one of them can be a rust overload, so i used value-initialization.the constructors are the
std::arrayoverloads that differ by more thanconstor&/&&, so i chose to use two non-member functions as examples to show that rule:to_arrayoverloads on&and&mutbuilt-in arrays, matching the c++ lvalue and rvalue versions.gettakes the element index as a template argument, and because the macro doesn't support const generics, each index gets its own overload through anIndex<I>tag type. all three indices were done for&, but only index 0 was done for&mutand by-value, since every other index works the same way. but for theconst&&overload it can't be represented, because rust types can't beconst.i implemented
getto use&'staticlikeforward_list::front, and its test leaks the arrays the same way theforward_listtest does.i skipped the other
std::arrayoverloads, with theScopesection in the module doc listing each one with the reason.ai use: i used claude (web) to get explanation across different parts of the codebase, especially the
overload!implementation and understanding of how overloads dispatch.the code implementation are mine, written after studying
forward_listimplementation, andstd::arrayspecs from c++.after studying
forward_listand the use of&mutfor c++ move, i was curious to ask if there have been any discussion about offering rust style move (by value)? i understand this would be a mismatch between both languages, but i'm curious for case when the moved-from c++ object itself needs not to be used after it is moved. because i initially implemented move to take ownership of theotherobject.closes #34
Limitationsdoc sectionErgonomicsdoc sectionIncompatibilitiesdoc sectionWorkaroundsdoc sectionInexpressible Overloadsdoc section