Repository navigation
Conversation
Quest-Claim: dda497bfe3dc255e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…acity pointer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…through realloc Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Design change: the maintainer picked a runtime (Written by Claude Opus 5.5 with high effort) |
|
Closed on the maintainer's call (2026-11-07): m3's unsafe policy refuses (Written by Claude Opus 5.5 with high effort) |

Finishes quest/m3/ts-unsafe/vec-from-raw-parts.md.
Problem
rust2-ts refused
Vec::from_raw_partsas an unsupported call. So aVeccould not adopt memory thatallocgave for aLayout::array::<T>(n). Typed allocation is a JS array ofTs since the epic's 2026-11-01 decision, and anrs.Vecholds std's capacity since moq-dev/rust2#555. smallvec'sDropandinto_vechand their spilled heap back this way.Approach
Vec::<T>::from_raw_parts(p, length, capacity)lowers tors.vecFromRawParts(p, length, capacity, size).ppoints at the start of a JS array: memory fromalloc(anrs.Block's array), or aVec's own items given up byManuallyDropormem::forget. The newrs.Vecholds that same array withcap = capacity. Everything pastlengthmoves into the array'srs.Room(moq-dev/rust2#623), so aset_lenback over slots written before the adoption reads them, as std's does. A room the array already had, from an earlierVec, carries over to the new one. A slotallocnever wrote stays unwritten.relength, shared withvecSetLen.Vec<u8>lowers tors.byteVecFromRawParts, which takes the sameUint8Arrayas its storage. Byte allocation fromallocstays refused, as the epic decided, so this covers a byteVec's own buffer.Vecwith the given capacity (Vec::from_raw_parts(NonNull::dangling().as_ptr(), 0, 0)).Box::into_raw's, gives aVecof an array of that one value. A runtimeWeakMap(HOMES, throughhomeOf) records the storage each such place was given, and the place is redefined to read and write that storage's slot 0. A write through either side shows through the other, and adopting the same value again reuses the storage, so a pointer from the firstVecsees writes through the second. Any later route that turns a lone value into array storage can reusehomeOf; onlyfrom_raw_partspays for it now. Bytes fromallocthat no cast typed (anrs.Block) give a byteVecnew storage of the capacity, since a write to them is refused.reallocof the memory under aVecnow carries over what theVec's room holds past its length (rs/alloc.ts). Before, it copied only the items array, so it lost what a pointer orset_lenhad put past the length.ub_checksmessage. That panic can't unwind, so it aborts, located atfrom_raw_parts_in's check (newvec_from_raw_partssite incaller.rs).rust2-ts:error (tests/pointers.ts).as_mut_ptr) and zero-sized elements.adoptmodule in thelang/heapfixture:adopt::allocated:u32s,Arc<u8>s (strong counts show each drop and leak), andOption<(u8, u16)>s. They are adopted fromallocwithLayout::arrayor an equalfrom_size_alignlayout, grown byreallocor not, with slots written past the length. EachVecthen pushes past the capacity, takes in the written slots byset_len, truncates, or shrinks.adopt::readopted:Vec<u32>,Vec<Arc<u8>>, andVec<u8>round-tripped throughManuallyDrop, with room written through the pointer. Also: an emptyVec's spare-capacity pointer; aVecgrown byreallocand adopted again;Vec<u32>andVec<u8>of aBox's value, written through both theVecand the pointer; the sameBoxadopted twice, foru32andu8, written through the secondVec, the firstVec's pointer, and theBox's pointer;Vec<u8>of bytes fromalloc; and the dangling emptyVec.panicsfixture's new arm 74 covers the abort.Impact
Vec::from_raw_partstranslates where it was refused.vecFromRawPartsandbyteVecFromRawParts.reallockeeps aVec's room.Vec::from_raw_partsis verbatim in constructs. The model census at the moq pin (ffcb7cd) lists noVec::from_raw_partsrefusal (50 refusals in all). Before this PR, smallvec'sDropfor its two instances (Weak<Waker>,NonNull<Segment>) reached it, per the quest.Choices made without asking
Vec's own buffer is adopted too, not only memory fromalloc. It is the same runtime path: the pointer isas_mut_ptr'srs.AtIndexinto the items, whose room the newVecinherits. It covers the commonManuallyDropround trip, and leaving it out would be a refusal with no simpler result.capacity * size_of::<T>()to equal the allocation's size, and breaking that is UB, so the runtime takes the capacity it is given. Only the byte path checks it, because aByteVec's capacity is its storage's length.allocalready refuses them, and the JS value of a zero-sized type is not alwaysundefined.rs.Blockstays registered. A later cast of theVec's pointer back to bytes fordeallocfinds it either way. Unregistering would change nothing a defined program sees.Decisions
Vec::from_raw_partsadopts (maintainer, 2026-11-07, at the round-3 design checkpoint): ✅ a runtimeWeakMapfrom the lone value's place to the array storage it was given, so adopting it again reuses that storage, built as a general mechanism any later route turning a lone value into array storage can reuse, and paid for only byfrom_raw_parts(rejected: make everyBox::into_rawpointer an array of one from the start; dropBoxadoption and throw; merge as is).Alternatives
rs.Vec: simpler, but a pointer taken before the adoption (p.add(i)) would no longer reach theVec's items or its room.Follow-ups
String::from_raw_parts(andString::from_utf8_uncheckedof an adoptedVec<u8>). Drop: no consumer at the moq pin, and byte allocation fromallocstays refused.Caused by
No single PR:
Vec::from_raw_partswas never supported.Validation
On head 3342dd820 (origin/main fd62edf merged in):
just ci origin/main(innix develop): lint and quest check pass, and so do the 8 unit tests, all 89 fixture tests (lang::dynstoo, since moq-dev/rust2#633 landed), and the 10 runtime tests.Boxcase against the round-3 runtime (noWeakMap): 164 differences in 841lang/heapcases; with it, 0.Earlier, on head b11502c:
just ci origin/main(innix develop): lint and quest check pass, and so do the 8 unit tests and 88 of 89 fixture tests. The one failure islang::dyns(ids.tsTypeId hashes), which depends on the checkout's path. Per the coordinator's known-issues list it reproduces on bareorigin/main(I did not rerun it there), and moq-dev/rust2#633 is fixing it.just test --test runtime, which cargo skipped after that failure, passes on its own (10 tests).lang/heapcase against the native build: 15,086 cases, 0 differences. Each runtime fix was checked by removing it in the copy under test, with the new cases in place:relength's room write: 149 differences in 1,586 cases;realloc's room copy: 313 differences in 1,586 cases;rehomeor lone-place adoption: 164 differences in 841 cases.panicsfixture at 1,500 inputs per case: 0 differences. With the length check moved past the capacity, it shows 14 differences.ffcb7cd: 50 refusals, none of themVec::from_raw_parts.Models
Size: S
Implementer: Claude Code, claude-opus-5-5, high effort
(Written by Claude Opus 5.5 with high effort)
🤖 Generated with Claude Code
Review
Rewritten by
quest reviewafter each round.01a1b680-e0d8-7cb0-b67b-bf7d533ee68d, round 2Both earlier findings are fixed and 15,373 heap differential cases pass, but adoption still breaks pointer identity for offset_from.ts/runtime/rs.ts:2654:[P2] Share adopted storage with pointer arithmeticts/runtime/rs.ts:2699:[P2] Allocate byte storage lazily and reuse the fallback;Lazy allocation in rawPartsOf and byteVecFromRawParts now produces zero capacity-sized allocation during reuse and exactly one buffer for fresh adoption, verified with Uint8Array instrumentation.ts/runtime/rs.ts:2679:[P2] Resolve standalone pointer homes before adopting their arrays;rawPartsOf now resolves ptrHome before selecting storage; both the byte arithmetic round trip and the non-byte write before set_len reproduce correctly at this head.