Rust: an update after a field read of the field it replaces builds, and the read moves - #1438
Merged
Merged
Conversation
…ual tail calls
A let in the body of a self tail call or of a mutual tail-call arm emitted
its value as it was, so a field read there always moved the field out of
its record. Mutual tail-call arms also ran without the field-move facts, so
an update after such a read did not know the record was partially moved:
progress = flight.progress
Flight.update(flight, progress = f(progress))
became `let progress = flight.progress; { let mut __updated = flight; ... }`,
which rustc rejects (E0382). A loop that read a field in a let and later
returned the whole record failed the same way.
These lets now own their value like every other binding: the field moves
where the field-move analysis allows it and is cloned otherwise. Mutual
tail-call arms apply that analysis too, so the update keeps the other
fields with `..flight`, and `f(flight.progress)` inside the update moves
the field instead of cloning it.
A local bound by a let to a movable field read now counts as owned for
own_param, so `f(progress)` hands `f` a Map it can update in place, the
same as `f(flight.progress)`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Porting btc-listener onto main hit E0382 in generated Rust:
inside
tended, a member of a mutual tail-call group, becameRoot cause
Two gaps, both on the tail-call paths only:
emit_mir_tco_body) and of a mutual tail-call arm (emit_mir_trampoline_body) emitted each binding value raw. A field read there always moved the field out, whateverfield_movessaid, where every other body goes throughemit_mir_binding_valueand clones a read that is not movable.apply_field_moves, somoved_rootswas empty and the update of Move record fields out at the record's last use in generated Rust #1428 took the "nothing was moved out" path, moving the whole record.The same raw move also broke a loop that reads a field in a let and later returns or updates the whole record (
progress = flight.progressthentrue -> flight); that shape failed before #1428 too.Fix
emit_mir_binding_valueas other bindings (a bareIntbinding and a discarded value are unchanged).f(progress)keptfon a borrowed Map that it cloned before inserting, whilef(flight.progress)graduated.Result on the btc shapes:
let progress @ _ = flight.progress; Flight { progress: dialledOn(progress)?, ..flight }andFlight { progress: dialledOn(flight.progress)?, ..flight }, both without a clone.Tests
rust_work_spec::an_update_after_a_field_read_of_the_replaced_field_builds_and_moveswith fixturetests/fixtures/rust_update_after_field_move: plain bodies, a self loop that reads a field and hands on the whole record, and a mutual pair (let form and inline form). It checks the emitted moves (no.clone()of the moved field,growtakes its Map by value, no__updated = flightafter a partial move), builds with cargo and compares with the VM.examples/,tests/fixturesandself_hosted/with amaincompiled to Rust andcargo checked: 151 pass, none fails with a move error. The 6 that fail (grok_s_language,http_server, 4 capability-manifest fixtures) fail the same way with the main binary (E0277/E0308, unrelated).tendedrewritten to both update forms, passcargo check.arm.bodyinstead of cloning it.rust_work_spec,own_param_graduation,own_param_soundness,rust_codegen_regression,compile_spec,shared_update_spec,verify_handle_params_spec, fmt, clippy.