Skip to content

Translate user defined moves - #369

Merged
nunoplopes merged 18 commits into
Cpp2Rust:masterfrom
lucic71:move-constructor-synthetization
Sep 17, 2026
Merged

nunoplopes merged 18 commits into
Cpp2Rust:masterfrom
lucic71:move-constructor-synthetization

Conversation

@lucic71

@lucic71 lucic71 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

This PR translates move constructors as ordinary constructors: fn S_pmutS(_a0: *mut S) -> Self.

The rules for translating and using the move constructors are:

No Move Constructor (MC) Copy Constructor Result
1 user-written implicit/defaulted/deleted translate MC and always use it
2 user-written user-written translate MC and always use it
3 implicit/defaulted implicit/defaulted don't translate MC and use .clone() on move
4 implicit/defautled user-written/deleted translate MC and always use it

For 1 and 2, we always use the translated move constructor because it might contain custom logic that needs to run on every move. For 3 we use .clone() because it has the same effect as a move would have. For 4 .clone() is not available (deleted) or might contain custom logic (user-written) so we synthesize an implicit/defaulted move constructor and use it.

std::move becomes a transparent construct, i.e. no move decision is taken at the time of the cast. It's only used by clang to choose the right constructor. Moves happen on CXXConstructExpr.

Each STL type has move construction rules. Rules that move using std::mem::take have their bodies rewritten as follows:

  • record has move constructor => rewrite std::mem::take as a call to the move constructor
  • record does not have move constructor and is copyable => convert as rvalue (without clone)
  • record does not have move constructor and is not copyable => convert as fresh rvalue (with clone)
  • record is STL type => use the rule for STL move constructor
  • temporary value => convert as rvalue

When the move constructor is user written (1) or the move constructor is
defaulted/implicit and copy constructor is user written (2).

The rationale for 2 is: if user-written move constructor is defaulted
then the first choice is to call .clone(). But if the copy constructor
is user written, then that's wrong. So force the synthetization of the
move constructor in that case.
@lucic71
lucic71 marked this pull request as draft September 14, 2026 20:16
@lucic71
lucic71 marked this pull request as ready for review September 15, 2026 08:22

Holder h1(4);
h1.p.reset(new int(9));
Holder h2 = std::move(h1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the translation of this is calling the copy constructor rather than move, I think.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's the move constructor. Copy constructor is deleted because Holder contains unique_ptr which is not copy

@nunoplopes

Copy link
Copy Markdown
Contributor

This strategy works while fields are boxed in Values, but otherwise we need move constructors to call std::mem::take to move objects and destroy (default initialize) the old one, otherwise we break the translation of classes for example that do reference counting.
I would also like to see more meaningful names in functions. It's very hard to follow the generated code.

@nunoplopes
nunoplopes merged commit 2f5de0c into Cpp2Rust:master Sep 17, 2026
9 checks passed
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