Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Add emplacement for cxx::UniquePtr. #27

Merged
merged 1 commit into from
Jan 26, 2022
Merged

Conversation

adetaylor
Copy link
Contributor

Users of cxx may well want to emplace into cxx's UniquePtr. This PR adds such support.

There are a number of issues yet to be resolved here. Specifically:

  1. The contract of emplace always returns a Pin<SmartPtr<T>>,
    whereas we want to return a simple UniquePtr<T>, because UniquePtr
    is always intrinsically pinned.

  2. Because of this mismatch, there are some nasty transmutes, and the test code is incomplete.

  3. As there appears to be no way to run a build script separately
    for tests, we are pulling in some C++ into the production code.

Still, it feels ready for an early look @mcy!

For users of plain cxx, there's a lot of boilerplate to be written. Of course, the idea is that 100% of the boilerplate here is created by autocxx. For testing purposes, though, this code depends only on cxx not autocxx.

src/cxx_support.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@mcy mcy left a comment

Choose a reason for hiding this comment

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

Hi Ade,

Thanks for the patch. I've left a mix of design comments (I think this is a little bit more complicated than it needs to be tbh) and style nit-picks.

Cargo.toml Outdated Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
src/cxx_support_test_cpp.h Outdated Show resolved Hide resolved
src/cxx_support_test_cpp.h Outdated Show resolved Hide resolved
src/cxx_support_test_cpp.h Outdated Show resolved Hide resolved
src/cxx_support_test_cpp.h Outdated Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
let uninit = std::mem::transmute(uninit_ptr.as_mut().unwrap());
let result = n.try_new(uninit);
if let Err(err) = result {
T::free_uninitialized_cpp_storage(uninit_ptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that this is not panic safe and will leak memory on panic. Most of the library isn't either but a // FIXME: Not panic safe might be appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to do better here using panic::catch_unwind, but we would need to constrain N to be UnwindSafe within the trait definitions of Emplace/EmplaceUnpinned so I gave up for now. Let me know if you think that would be good or not?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm ok with dealing with it later. The right way to do this is actually to have a type that captures the uninit_ptr and frees it in its destructor, which you forget() at the end of the scope. Unwinding out of this frame will run the destructor.

I need to go through the whole crate and add various panic guards so I wouldn't worry about it right now.

T::free_uninitialized_cpp_storage(uninit_ptr);
return Err(err);
}
// TODO - is it possible to remove the need for Emplace to return a pinned
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that, in general, we can't do this. T::emplace is analogous to Box::pin for API purposes... the real issue is that UniquePtr should just provide a function for unpinning itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had a crack at this before seeing your comment, and added a commit which attempts to make this work.

Logic in my head is:

  • Pin<Box<T>> should be the thing which implements a fundamental emplace-y trait. Fundamentally, Box<T> is not safe to emplace into; only Pin<Box<T>> is safe to emplace into.
  • For convenience, Box<T> should actually implement some other trait, which calls through to Pin<Box<T>>::emplace, so that callers can say Box::emplace_pinned(...) or similar.

That's the idea. In the commit I pushed, emplace_pinned is also called emplace which gets confusing, but this was to avoid breaking compatibility more than necessary. LMK your thoughts.

src/cxx_support.rs Outdated Show resolved Hide resolved
src/new/mod.rs Outdated
Comment on lines 148 to 150
/// This enables an `emplace()` method for [`Box`], [`Rc`], and [`Arc`]. Users
/// are encouraged to implement this function for their own heap-allocated smart
/// pointers.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment needs to be moved, I think, plus a clarification on why the trait has been split.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@mcy mcy self-requested a review January 5, 2022 15:34
Copy link
Contributor

@mcy mcy left a comment

Choose a reason for hiding this comment

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

Pretty close. Just nits at this point. =)

cxx-tests/Cargo.toml Outdated Show resolved Hide resolved
cxx-tests/Cargo.toml Outdated Show resolved Hide resolved
cxx-tests/Cargo.toml Show resolved Hide resolved
src/cxx_support.rs Outdated Show resolved Hide resolved
let uninit = std::mem::transmute(uninit_ptr.as_mut().unwrap());
let result = n.try_new(uninit);
if let Err(err) = result {
T::free_uninitialized_cpp_storage(uninit_ptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm ok with dealing with it later. The right way to do this is actually to have a type that captures the uninit_ptr and frees it in its destructor, which you forget() at the end of the scope. Unwinding out of this frame will run the destructor.

I need to go through the whole crate and add various panic guards so I wouldn't worry about it right now.

src/new/mod.rs Outdated Show resolved Hide resolved
cxx-tests/src/cxx_support_test_cpp.h Outdated Show resolved Hide resolved
Cargo.toml Show resolved Hide resolved
Copy link
Contributor

@mcy mcy left a comment

Choose a reason for hiding this comment

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

I think this is basically good enough to merge modulo outstanding nits. Would you mind squashing your history into one or two descriptive commits so that I can rebase them?

cxx-tests/src/tests.rs Outdated Show resolved Hide resolved
cxx-tests/src/tests.rs Show resolved Hide resolved
Cargo.toml Show resolved Hide resolved
This allows cxx::UniquePtr::emplace of any New made using
moveit norms.

UniquePtr is different from the other smart pointers supported
by moveit, in that UniquePtr is _intrinsically_ pinned. You can't
get an unpinned mutable reference to its contents; therefore there
is no need to use Pin<UniquePtr>. This commit therefore splits
the Emplace trait into Emplace and EmplaceUnpinned, with the goal
that users can naturally call {Box, Arc, Rc, UniquePtr}::emplace
in a similar fashion, so long as both traits are in scope.

UniquePtr<T> emplacement support requires the T to implement
a new trait, MakeCppStorage, which can provide uninitialized
space for the T in some heap cell which can later be deleted
using the std::unique_ptr default destructor. It's not expected
that most users will implement this trait, but that it will
instead be generated automatically by code generators atop moveit
and cxx (for instance, autocxx).

Testing these cxx bindings requires inclusion of C++ code,
and we don't want to link against C++ code for the production
rlib - therefore a separate module is created within the workspace
to host these tests.
@adetaylor
Copy link
Contributor Author

Duly rebased into a single descriptive commit (and your two remaining nits addressed). Thanks!

@adetaylor
Copy link
Contributor Author

Are you waiting for anything from me here @mcy? I have an autocxx pull request (still WIP) which depends on this (google/autocxx#742)

@mcy mcy merged commit db655d5 into google:master Jan 26, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants