Skip to content

feat(ts)!: rs.Slice for references to arrays, comparing element places, and unsizing an Arc's array - #584

Merged
kixelated merged 22 commits into
mainfrom
quest/m3/ts-slice-object
Nov 4, 2026
Merged

kixelated merged 22 commits into
mainfrom
quest/m3/ts-slice-object

Conversation

@kixelated

@kixelated kixelated commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Part 2 of quest/m3/ts-slice-object.md, after #576.

Problem

A &[T; N] or &mut [T; N] of values other than bytes was the JS array itself. So the run that first_chunk_mut, split_first_chunk_mut, a subslice pattern, and array::from_mut lend had to be an arrayView Proxy over the slice, and a shared one was a copy. That meant:

Approach

A &[T; N] is now an rs.Slice of its elements where they are, as &[u8; N] already was an rs.ByteSlice. Pointee::Bytes is now Pointee::Array, and each byte-only path (bytes_ref, in_bytes, the lend boundary) covers any element type, picking byteSlice* or slice* by element.

  • Lend boundary. At the boundary with std's mappings, lend.rs converts both ways with sliceFrom and sliceItems/sliceView, as it did for bytes. It also now:
    • lends to closures whose items are arrays a closure returned (filter over map);
    • gives map keys of other values than bytes as their elements;
    • leaves Option::get_or_insert* to MIR lowering.
  • Writes. A whole write through a &mut [T; N] writes each element in place (sliceSet). mem::replace/swap/take go through replaceIn(sliceView(..)), which keeps the copy depth sliceIn's arrays need.
  • Pointer comparison. A pointer to a [T; N] compares by array and offset through any runs view (sliceSame). Comparing pointers to elements and other places of values JS copies stays refused at translation, as on main, since JS may make two rs.Places for one place. Comparing pointers to a [T; 1] is refused too: array::from_ref, from_mut of a variable, and Option::as_slice make an array of one of their own for a value, so only longer arrays are always where Rust's are. This also closes that gap for [u8; 1], which main compared. A shared as_chunks or as_rchunks lends copies of its runs that answer to their view of the slice, so pointers to them compare where the run is.
  • Arc unsizing. An Arc, Rc, or Weak of an array unsizes to its slice as an rs.ArcDyn that shares its count, as trait objects already do. ptr_eq of slices compares the Arcs they were made from.
  • Copies. A clone or copy of an array that the runtime holds as a value takes the array, not a slice (clone_fn, array_copy). objectCopy copies the runs a view of runs holds. Before this, a mem::replace of a run of runs kept views that later writes changed.
  • Format arguments. format_args!'s array is read through the slice that a reference to it now is. An argument read once is folded in place, so it allocates no array (["no case ", (f) => ...] rather than main's args2[0]).

Removed: the reject fixture's three cases that now translate (array pointer comparison and both Arc/Rc array unsizes). Added: comparing pointers to an array of one is refused (pointers::array_of_one), and a trait object of an array of other values than bytes is refused, as a byte array's already was, because a box holds the array and a reference holds a slice of it.

Impact

Consumer: moq. just ts moq translates and type-checks, and just ts bench (moq's codec, four engines) shows no measurable cost:

engine translated ÷ main (geomean, 73 workloads) translated ÷ A/A
Node 24 1.004 1.005
Bun 1.4 0.995 1.016
Chromium 155 1.008 1.001
Firefox 158 1.005 1.000

Where single workloads moved beyond that, the A/A route moved as much (Bun btree/256 1.38 vs A/A 1.28, Chromium push/deque 1.44 vs 1.48). The bundle grows by 927 B raw and 204 B gzipped. Measured before the review fixes: since then the element-place comparisons were dropped, and each shared as_chunks run gained a view tag.

Public API:

  • A &[T; N] or &mut [T; N] of values other than bytes, and a raw pointer to one, is now an rs.Slice<T> rather than T[]. A hand-written caller or binding passes and reads slices (see fixtures/crates/bind/slots.ts). rs.Ptr<T> admits the rs.Slice of an array.
  • Runtime added: sliceSame, sliceSet, sliceView, slicePlaces, and sliceToArray's optional element copy.
  • Comparing pointers to a [T; 1] is now refused, [u8; 1] included.
  • A trait object of a [T; N] is now refused (it translated before for other values than bytes).
  • Runtime removed: arrayIn, arrayOfPlace, arrayPlaces, sliceFirstChunk(Mut), sliceSplitFirstChunk(Mut), sliceLastChunkMut, and sliceSplitLastChunkMut. arrayView is no longer exported. sliceLastChunk and sliceSplitLastChunk return slices.

Decisions

✅ Decided with the maintainer on 2026-11-04: generated code fails at run time only where Rust panics. A pointer comparison the translator can't answer exactly is refused at translation with a span, so this PR adds no runtime error. The exact slower path for the comparisons left refused is planned in quest/m3/ts-runtime-throws.md (#598), not here.

Follow-ups

These remain in the quest:

  • as_chunks_mut's runs still use the Runs/ByteRuns views, because a slice of arrays holds each array as a JS array of its own.
  • slice::from_mut of a &mut u8.
  • Comparing *const [T].

Review

  • Differential tests:
    • lang::pointers::arrays: array pointers made by every lending path (subslices, chunks, windows, iterators, shared as_chunks runs, Box, Option, Rc, RefCell, HashMap, fields, an unsized Arc), compared and written through, and a nested ptr::read.
    • lang::shares::unsized_arrays: Rc/Arc/Weak of arrays unsized, counted, written, compared, and dropped by whichever owner is last.
  • just ci and just ts moq passed locally on the merge with main at a392514.
  • quest review (Codex) is clean after round 2 of this revision. Fixed along the way:
    • ptr::read of nested arrays now copies them.
    • Arc<[T; N]> to Arc<dyn Trait> no longer takes the slice path. An array as a trait object is refused, as byte arrays already were.
    • The runtime throw for comparing places is gone; those comparisons are refused at translation again.
    • Shared as_chunks runs compare where they point, and arrays of one are refused rather than compared wrong.

🤖 Generated with Claude Code

kixelated and others added 5 commits November 4, 2026 03:43
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ding gaps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	ts/src/lower/mir/rvalue.rs
#	ts/src/lower/mod.rs
#	ts/src/lower/pointer.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title rs.Slice feat(ts)!: rs.Slice for references to arrays, comparing element places, and unsizing an Arc's array Nov 4, 2026
kixelated and others added 7 commits November 4, 2026 07:09
… refuse array objects

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…argument in place

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comparing pointers to places JS may make two of for one stays refused
rather than failing at run time; only pointers to arrays compare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 10 commits November 4, 2026 10:04
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lace

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f one

Shared as_chunks and as_rchunks runs answer to their view of the slice, so
pointers to them compare where they point. An array of one may be one JS
made of its own for a value, as array::from_ref does, so comparing pointers
to one is refused at translation.

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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 4, 2026 20:17
@pullfrog

pullfrog Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

This run was cancelled before it finished.

Pullfrog  | View workflow run | via Pullfrog

@kixelated
kixelated enabled auto-merge November 4, 2026 20:17
@kixelated
kixelated added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit fc56c13 Nov 4, 2026
7 of 8 checks passed
@kixelated
kixelated deleted the quest/m3/ts-slice-object branch November 4, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant