Repository navigation
feat(ts)!: rs.ArraySlice for slices of arrays, replacing the as_chunks_mut proxies - #608
Conversation
Quest-Claim: f7589f7b711c512c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in, and pass as slices to std's other functions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eir elements' type Constants, defaults, Option::as_slice, a Cow's borrow, and the erased SliceOf and Ptr are runs too; to_vec reads runs as they are; a wrapped array is an element, not runs; and runs copied out and written back copy an element only as its type says: a Copy object's copy, or objectCopy of one a write lands in, never a reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nitialized MaybeUninits, and joined slices of arrays Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…they are Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tive Rust Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Matches rs.ByteSlice and rs.RawSlice, as the maintainer decided at the round-3 design checkpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Design change: after the round-3 checkpoint, the maintainer chose to continue with local fixes and to rename the type.
Rejected: merge as is; (Written by Claude Opus 5.5 with high effort) |
…s of arrays VecDeque's as_slices, as_mut_slices, and make_contiguous and BinaryHeap's as_slice of arrays lend an rs.ArraySlice of their own arrays, and a raw pointer to a slice of arrays is an rs.RawArraySlice of it, so Box::into_raw and from_raw give back the same storage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # ts/runtime/rs/collections.ts
…d a borrowed Cow, and split arrays lent lazily Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aw pointer A write through it landed in a copy where the pointer was into part of an array, or into arrays as_chunks_mut split from one slice, which no JS array holds alone. A shared reborrow still reads the array. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
|
quest land watch: the merge queue removed this pull request at 8b3cee4 because it conflicts with The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs. (written by quest land watch) |
|
quest land watch: armed auto-merge on this pull request at 29cd81c, so it joins the back of the merge queue once its required checks pass, with the land fixer's fix ( (written by quest land watch) |
|
quest land watch: the merge queue removed this pull request at 29cd81c because it conflicts with The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs. (written by quest land watch) |
|
quest land watch: armed auto-merge on this pull request at 64ed907, so it joins the back of the merge queue once its required checks pass, with the land fixer's fix ( (written by quest land watch) |
UnsafeCell::raw_get now translates only when every origin of its pointer is a known place of its cell: &raw or a reference of a local or field, through copies, casts, and derefs of such pointers, or MaybeUninit::as_ptr. A parameter, a Vec's or slice's pointer, null, and pointer arithmetic are refused with a span. raw_get returns to the eager form; the lazy place, the mixed-origins handling, and rawUnsize's pointer-to-no-memory case go. MaybeUninit::assume_init_mut is refused where its &mut is a place but one to what it holds is the object, as of an UnsafeCell<u32>. From #608, arrays of arrays keep main's representation: Cell::from_mut of one is cellOfArraySlice and Cell's writes of one rebind it. Its UnsafeCell::from_mut and Cell::swap are refused, as the cell may read a copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Part 3 of quest/m3/ts-slice-object.md, after moq-dev/rust2#576 and moq-dev/rust2#584.
Problem
A slice of arrays had each array as a JS array of its own, so the arrays
as_chunks_mutandas_rchunks_mutlend from a flat slice had no JS array to be. They were aProxy(Runs,ByteRuns) whose elements werearrayViewproxies orsubarrayviews of each array, made and cached on first read, with detaching copies before a whole write. In a micro-bench of the codec bench'spacketsworkload, that cost 10-20x reading each array where the flat buffer holds it.Approach
rs.ArraySlice<S>, is every&[[T; N]],&mut [[T; N]],&[[T; N]; M], andBox<[[T; N]]>. Elementiis the slice of arrayi: anrs.Slice<T>, anrs.ByteSlicefor bytes, or anrs.ArraySlicefor arrays of arrays. It reads and writes arrayiwhere it is, whether each array is a JS array of its own (itemsand theofthat lends one, as aVec<[T; N]>or[[T; N]; M]holds them) ornelements of one flat slice (asas_chunkssplits it). NoProxy.iter/iter_mut,split_at_mut,chunks_mut,windows,split*,first_chunk*,swap, ...) map to the runtime'sarraySlice*functions (ts/src/lower/array_slice.rs'snative). Any other std function given a shared slice of arrays readsrs.arraySliceAsSlice: the arrays themselves, so a callback'sptr::eqsees the array it was lent, or copies of thoseas_chunkssplit from one flat slice, which have no JS array of their own. A&mutone readsrs.arraySliceBy's copies, written back once it returns, as Rust moves their elements. A function that would return a borrow of those copies is refused with a span.as_chunks/as_rchunks(shared and mutable) andslice::from_mut/array::from_mutof a&mut [T; N]lend anrs.ArraySliceof the same storage.Cell::from_mutof an array of arrays isrs.cellOfArraySlice.mem::replace/mem::swapof an array,clone_fromof arrays of arrays and of aBox<[[T; N]]>, raw pointers to arrays of arrays, andDebugof their iterators all go through it.rs.arraySliceBy,rs.arraySliceReplace,rs.arraySliceSwap,rs.cellOfArraySlice) copies its elements by the element type (copy::elem_copy): the type's copy shim where JS keeps aCopyvalue as an object,rs.objectCopywhere a write lands inside the object, or none. So the copy shares no object with the arrays it was read from, and a&Cellthat does share one is untouched.rs.ArraySlicetoo: constants andDefault,Option::as_slice/as_mut_sliceof an array payload (rs.optionAsArraySlice),Cow<[[T; N]]>(rs.Cow<T, rs.ArraySlice<S>>, borrowed byrs.cowArraySlice),assume_init_ref/assume_init_mutofMaybeUninitarrays andBox::assume_init(rs.arraySliceOfSlice), generic structs at an array instance, andconcat/joinparts and separators.joinlends an array separator as it is, since it clones it anyway, so each element'sClonereads the separator's own.rs.ArraySliceof the same storage too:VecDeque'sas_slices/as_mut_slices/make_contiguous(rs.dequeArraySlices,rs.dequeArrayMakeContiguous),BinaryHeap::as_slice, an array pattern's rest of an owned array of arrays, and a borrowedCow's payload when matched. A raw pointer to a slice of arrays is anrs.RawArraySliceof it, soBox::into_raw/Box::from_rawgive back the same arrays; making one from a pointer to arrays stays refused, as onmain. The split iterators lend each array only when they read it.&mut *pof aMaybeUninitof an array through a raw pointer is refused with a span: whereppoints into part of an array, or into arraysas_chunks_mutsplit from one slice, no JS array holds it alone, and a write through a copy was lost (onmaintoo, for part of a plain array). A shared reborrow still reads it.Runs/ByteRunsproxies andView.detachare gone.arrayViewremains only where a&mut [T; N]into part of an array is taken whole as a JS array; that remainder is now the whole quest.Impact
rs.ArraySlicerather than anrs.Slice<T[]>/rs.Slice<Uint8Array>. Pass one asrs.arraySliceFrom(arrays, rs.sliceFrom)(rs.byteSliceFromfor bytes,rs.arraySliceOf(rs.sliceFrom)for arrays of arrays) and read one asrs.arraySliceItems(slice).ts/README.mddocuments it.ArraySlice,ArrayRef,LentArray,arraySlice*(arraySliceFrom,arraySliceOf,arraySliceAt,arraySliceItems,arraySliceAsSlice,arraySliceBy,arraySliceSet,arraySliceReplace,arraySliceSwap,arraySliceIter,arraySliceSplit*,arraySliceChunks*,arraySliceWindows, ...),cellOfArraySlice,RawArraySlice,rawArraySliceOf,dequeArraySlices,dequeArrayMakeContiguous,arraySliceBoxCloneFrom,arraySliceBoxCloneFromFn,arraySliceCopy,ElemCopy,optionAsArraySlice,arraySliceOfSlice, andcowArraySlice.rs.Cowtakes a second type parameter, its borrowed form,rs.Slice<T>by default.arraySliceSwap,arraySliceBy, andarraySliceReplacetake the element copy as an argument. RemovedbyteSliceAsChunks,byteSliceAsChunksMut,byteSliceAsRchunks,byteSliceAsRchunksMut,sliceAsChunksMut, andsliceAsRchunksMut.sliceAsChunks/sliceAsRchunksnow returnrs.ArraySlice.Decisions
Decided with the maintainer on 2026-11-06:
&mut [[T; N]]holds its arrays: ✅ (a) a runtime slice-of-arrays type for every slice of arrays, nested or flat, which replaces theRuns/ByteRunsproxies (rejected: (b) eagersubarrayviews for bytes only, keeping theProxyfor other elements; (c) keep the proxies and drop the item). The quest narrows to it and is sized [L].slice::from_mut/array::from_mutof a&mut u8: ✅ kept, deferred to its own quest at the end of m3, quest/m3/ts-byte-slice-place.md (rejected: drop it until a consumer appears).*const [T]of values JS copies: ✅ moved into quest/m3/ts-runtime-throws.md with the form_urlencoded 1.2.2debug_assert!(ptr::eq(..))finding (rejected: wait on rs.String's UTF-8 route).Decided with the maintainer on 2026-11-07, at the round-3 design checkpoint (two medium findings, both conversions that copied arrays where Rust borrows them):
rs.ArraySliceonly for whatas_chunkssplits, keepingrs.Slice<T[]>for other slices of arrays; drop). Where each array is a JS array of its own,rs.arraySliceItemsslices the list of them instead of copying each, so arrays keep their identity for std callbacks such asbinary_search_byand forptr::eq.joinreads its separator without copying it. The arraysas_chunkssplits from one flat buffer have none of their own, so a callback still gets a copy; thatptr::eqgap is recorded in quest/m3/ts-runtime-throws.md.rs.Runstors.ArraySlice, matchingrs.ByteSliceandrs.RawSlice, with its helpersrs.arraySlice*(rejected:rs.Slices,rs.Chunks, keeprs.Runs).The quest file keeps only the
arrayViewremainder, sized [M].Choices made without asking:
array_slice::giveconverts a slice of arrays for every std function that isn't a trait's method, not just[T]'s own methods, soVec::extend_from_slice(&[[T; N]])and the like get the slice they read.rs.sliceToArray(an earlier head) also copied arrays a&Cellshares, which Rust doesn't.rs.sliceItemskeeps one signature forrs.Slice, and the translator emitsrs.arraySliceItemsfor an array slice. An overload maders.sliceItemslose its type when passed as a function, asOption::copiedpasses it.MaybeUninitarray through a raw pointer is refused for every non-byte array, not only arrays of arrays, since part of a plain array loses the write the same way and no fixture used the path.rs.ArraySlicelends each array as isrs.ArrayRef(wasRun), the array it lendsrs.LentArray(wasRunArray;rs.ArrayOfwas taken), the element copyrs.ElemCopy(wasRunCopy), and the oldrunsArray/runsItemsarearraySliceItems/arraySliceAsSlice, aftersliceItemsandas_slice. The translator's module islower/array_slice.rs.Bench
Rerun after the round-3 checkpoint's fixes and the rename:
just ts benchat 06e0753, with--route main=origin/main f56859c's translation (.tmp/bench.shundernix develop .#bench). Nanoseconds per packet, median ± half the IQR:packets/ts/numbermain → PRpackets/ts/number-copymain → PRThe speedup holds: 2-8x on
packets/ts/numberand 1.8-2.7x onnumber-copy. Only the twopacketsloops differ in the generated code (rs.arraySliceIter(rs.sliceAsChunks(..))forrs.iterLend(rs.iterSlice(rs.sliceItems(rs.byteSliceAsChunksMut(..))))). Every other family's geometric mean of PR ÷ main is 0.73-1.21, within its runtime's A/A spread over a family's geometric mean (12-35%); their translated code is unchanged and only the runtime helpers differ.Validation
just ciat 8b3cee4: passes (fixtures 89/89, including the lang fixtures' differential tests against native Rust).slices::array_formscovers each form a slice of arrays takes;slices::array_identitypins a callback'sptr::eqon a lent array (of bytes, words, and arrays, of all of a slice and of part of it) andjoin's separatorClonereading its own address, each failing against native Rust with its fix reverted (74 and 79 differences);slices::array_sourcescovers the deque, heap,Rc,Vec::leak, raw-pointer round trip,MaybeUninitreborrow, andCow::Borrowedcases;arrays::restscovers rest patterns written through.reject'spointers::uninit_rows_mutandpointers::uninit_part_mutpin the new refusal.Alternatives
subarrayviews (decision 1b): 3-4x the flat form, and it keeps theProxyfor other element types.rs.Sliceper array made eagerly: allocates every array's slice up front;rs.ArraySlicemakes one only when it is read.Follow-ups
arrayViewremainder) and quest/m3/ts-byte-slice-place.md, both in this PR.Models
Size: L
Implementer: Claude Code, claude-opus-5-5, unknown effort
Implementer: Claude Code, claude-opus-5-5, high effort
Review
mechanical fix by codex, gpt-6.1-sol, medium effort:
Merged base and preserved both sides in four content conflicts; regenerated ts/fixtures/lang/manual/expected/uninit.ts; passed UPDATE_EXPECT=1 just ts test --test fixtures lang::manual -- --exact, just check base, and just test base.(head 64ed907)mechanical fix by codex, gpt-6.1-sol, medium effort:
Merged base and re·solved three generated TypeId snapshot conflicts using base normalization; ran just check base and just test base (passed).(head 29cd81c)Recorded by hand:
quest reviewran from a local branch not named after the quest and printedrecord skipped.01a1b4bb-259f-7702-9a0a-f128a9b641dc, round 3, started after the round-3 design checkpoint of the earlier sessionThe reported write-loss path now produces a compile error with a source span, both rejection probes pass, and no further defects were found.ts/src/lower/mir/rvalue.rs:67:Preserve flat storage through mutable wrapped reborrows; refused with a span for every non-byte array.ts/src/lower/mod.rs:1503:Adapt owned-array rest borrows to ArraySlice; fixed: medium,ts/src/lower/mir/rvalue.rs:66:Convert wrapped multidimensional-array reborrows correctly; fixed: medium,ts/src/lower/mod.rs:1698:Update borrowed Cow payload extraction for ArraySlice; fixed: medium,ts/runtime/rs/iter.ts:480:Lend split-iterator rows lazily.01a1b47a-a145-7b00-9697-4a5b4b214e02, Codex gpt-6.1-sol at high effort, since it read the size as M) found three at 06e0753: the deque's slices and the raw-pointer round trip, both fixed, andptr::eqofas_chunksrows in a callback, refuted by decision 4.🤖 Generated with Claude Code