Skip to content

proxy-types: Remove requirement for return types to be default-constructible - #337

Open
ryanofsky wants to merge 4 commits into
bitcoin-core:masterfrom
ryanofsky:pr/noreturn
Open

proxy-types: Remove requirement for return types to be default-constructible#337
ryanofsky wants to merge 4 commits into
bitcoin-core:masterfrom
ryanofsky:pr/noreturn

Conversation

@ryanofsky

Copy link
Copy Markdown
Collaborator

Improve clientInvoke to avoid requirement that return types are default-constructible. This lets bitcoin/bitcoin#10102 return types like PartiallySignedTransaction, WalletAddress, util::Result that don't have default constructors, without needing to wrap them or give them default constructors.

Also remove requirement that return types are copyable or movable, and add tests for this, and improve documentation for ReadDest classes to describe how they are supposed to be passed to ReadField callers and used inside CustomReadField implementations to deal with different types including non-movable, non-default-constructible ones.

ryanofsky and others added 4 commits August 3, 2026 18:29
…turn types

Previously the generated client declared a default-constructed result variable
and passed a reference to it into clientInvoke, requiring the return type to be
default-constructible. Restructure the client return path so the value is
constructed directly in place:

- clientInvoke takes the return type and result Accessor as explicit template
  arguments (defaulting to void). It deserializes the result with ReadField
  into a ReadDestTemp, so the value is built from constructor arguments without
  a default constructor.
- Propagate the value as a prvalue: IterateFieldsHelper::handleChain now returns
  decltype(auto), and the movable path uses C++17 guaranteed copy elision to
  construct the result in AlignedStorage.
- For non-movable return types (no move or copy constructor), copy the capnp
  response to a flat word buffer on the event-loop thread and deserialize it on
  the client thread, returning a prvalue via guaranteed copy elision so no move
  constructor is ever needed.

Add AlignedStorage<T>, a typed wrapper around an aligned byte buffer with a
ptr() accessor, used immediately by clientInvoke and reused later by TryFinally.

Extend FunctionTraits to all four PMF cv-qualifier combinations so clientInvoke
can derive the capnp Results type from the request method pointer.

The code generator emits clientInvoke<Result, Accessor>(...) for non-void
methods and plain clientInvoke(...) for void methods.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restructure result handling on the server side to eliminate
move-constructor requirements for IPC return types. (Client side
move-constructor requirements were removed in the previous commit.)

- Add 3-arg TryFinally(fn, after, consume) overload in util.h that stores
  fn()'s return value via placement new from prvalue (C++17 guaranteed copy
  elision) and passes it by reference to consume(), then destroys it.
  This avoids ever needing to move the result.
- Merge ServerRet into ServerCall by making ServerCall a template parameterized
  on the result Accessor (void for void methods). The result is now serialized
  inside the TryFinally after() callback, forwarding it with the invoked
  method's value category so move-only results (e.g. vector<unique_ptr<Bar>>)
  are moved rather than copied.
- Update code generator to emit ServerCall<void>() or ServerCall<Accessor>()
  instead of Make<ServerRet, Accessor>(ServerCall()).

Behavior-preserving: results are serialized exactly as before.

These changes are combined because they are tightly coupled: redesigning
TryFinally to return void forces ServerCall::invoke() to also return void,
which breaks ServerRet's existing `auto&& result = Parent::invoke(...)` binding.
The only clean intermediate would have ServerRet duplicate the same logic that
ServerCall<Accessor> ends up with, so combining produces a simpler result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Pinned<T>, a type with no default constructor and no copy or move
operations, to exercise the non-movable IPC return path end-to-end. Its
CustomReadField uses read_dest.construct() with a ReadDestTemp<T> argument,
which is the only way to deserialize a type that can neither be
default-constructed (for update()) nor moved (for a std::optional staging
variable).

Add two FooInterface methods:
- returnPinned returns Pinned<std::vector<int>> by value, verifying the client
  can retrieve a non-movable return value.
- throwPinned throws Pinned<std::vector<int>> via $Proxy.exception, verifying
  the exception path constructs a non-movable value as a prvalue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the terse ReadDestEmplace comment with structured documentation of the
three ReadField destination types (ReadDestEmplace, ReadDestUpdate, and the
ReadDestTemp() helper), covering:

- The contract for CustomReadField implementors: return decltype(auto) and
  forward the construct()/update() return value, which is easy to miss because
  most callers ignore it but is load-bearing when ReadDestTemp() is used.
- The contract for emplace callbacks and the three return-type cases (container
  emplace, ReadDestTemp() prvalue, and the vector<bool> reference-like proxy).
- When ReadDestTemp() is merely convenient versus strictly necessary, with the
  Pinned<T> return-value and nested-CustomReadField cases as examples.
- Return-value notes on each construct()/update() method and an inline note
  flagging the placement-new exception-safety gap in ReadDestUpdate::construct.

Comment-only change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DrahtBot

DrahtBot commented Aug 7, 2026

Copy link
Copy Markdown

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline and AI policy for information on the review process.
A summary of reviews will appear here.

@ryanofsky ryanofsky mentioned this pull request Aug 7, 2026
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.

2 participants