Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4d36f49
chore(quest): claim quest/m3/ts-string-capacity.md
kixelated Nov 4, 2026
aa2e239
bench(ts): str/ workloads, and prototype routes of a String
kixelated Nov 4, 2026
b819a89
Merge remote-tracking branch 'origin/main' into quest/m3/ts-string-ca…
kixelated Nov 4, 2026
8458a07
chore(ts): name the prototypes' runtime rs, after main's rename
kixelated Nov 4, 2026
e15faf9
Merge remote-tracking branch 'origin/main' into quest/m3/ts-string-ca…
kixelated Nov 4, 2026
b6a4e59
Merge remote-tracking branch 'origin/main' into quest/m3/ts-string-ca…
kixelated Nov 4, 2026
af58874
feat(ts): translate String as rs.String, with std's exact capacity
kixelated Nov 4, 2026
a91c421
style(ts): format
kixelated Nov 4, 2026
b597a20
chore(quest): drop the slice quest's empty Related
kixelated Nov 4, 2026
22a87cf
fix(ts): clone String::from(&String), and keep a hook's String payload
kixelated Nov 4, 2026
89f58fd
chore(quest): drop the panic payload quest for the PR's follow-ups
kixelated Nov 4, 2026
01ef4e5
Merge remote-tracking branch 'origin/main' into quest/m3/ts-string-ca…
kixelated Nov 4, 2026
52a2022
fix(ts): pass a dyn shim's &mut receiver as std's mapping takes it
kixelated Nov 4, 2026
5cd21cd
Merge remote-tracking branch 'origin/main' into quest/m3/ts-string-ca…
kixelated Nov 6, 2026
e6f5492
perf(ts): key a hash collection of text by the text itself
kixelated Nov 6, 2026
0627238
fix(ts): clone a str into a String, as std's clone_into clears and pu…
kixelated Nov 6, 2026
ed51944
Merge remote-tracking branch 'origin/main' into quest/m3/ts-string-ca…
kixelated Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ generic std gap is planned here even when moq never calls it.
- [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library
- [rs.Slice](/quest/m3/ts-slice-object.md) - references to arrays of other values are a runtime slice too, and pointers compare by where they point
- [Writes through iter_mut without a Place per item](/quest/m3/ts-iter-mut-places.md) - a loop over `iter_mut()` of copy values writes the slice's array by index
- [rs.String](/quest/m3/ts-string-capacity.md) - `String`, `Box<str>`, and `&str` are a runtime type holding std's capacity
- [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) - no runtime `rust2-ts:` error where native Rust goes on: an exact slower path, or a refusal at translation
- [rust2-ts with no ts.toml](/quest/m3/ts-configless.md) - a crate's whole `pub` API translates with no config and no bound items
- [Buf on a deque of bytes](/quest/m3/ts-vecdeque-buf.md) - `bytes`' `Buf` for a deque of bytes translates
Expand Down
4 changes: 0 additions & 4 deletions quest/m3/ts-panic-payload-capacity.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,3 @@ message). About 64 runtime sites pass such a message. Build each as std's `forma
without slowing the paths that never panic.

Public API: the runtime's panic sites, not their messages.

## Required

- [rs.String](/quest/m3/ts-string-capacity.md) - `String` holds std's capacity, which a hook then reads
4 changes: 0 additions & 4 deletions quest/m3/ts-slice-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,3 @@ stays refused, as do pointers to an array of one, and their exact path is

Public API: the translator's output and what a hand-written caller passes for a slice of
arrays.

## Related

- [rs.String](/quest/m3/ts-string-capacity.md) - the other native mapping becoming a wrapper
43 changes: 0 additions & 43 deletions quest/m3/ts-string-capacity.md

This file was deleted.

3 changes: 0 additions & 3 deletions ts/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 36 additions & 25 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ crate translation reaches is refused, and so are two versions of one crate it re

| Rust | TypeScript |
| --- | --- |
| `bool`, `str`, `String`, `Box<str>` | `boolean`, `string` |
| `bool`, `str`, `Box<str>` | `boolean`, `string` |
| `String` | `rs.String`: its text, `v`, its length in UTF-8 bytes, `len`, and the capacity std's holds, `cap`; `rs.stringFrom(text)` makes one of a JS string with room for exactly its bytes |
| `char` | `rs.char`: a `number` branded so, its Unicode scalar value |
| `u8`-`u32`, `i8`-`i32`, `usize`, `isize` | `rs.u8` and so on: a `number` branded with its type, checked: overflow panics |
| `u64`, `i64`, `u128`, `i128` | `rs.u64` and so on: a branded `number` while a safe integer, 32-bit limbs past 2^53, checked the same way |
Expand All @@ -107,8 +108,8 @@ crate translation reaches is refused, and so are two versions of one crate it re
| tuple, `[T; N]`, `&[T; N]` | an array, and `rs.Slice<T>` of all of it for `&[T; N]`; for bytes, `u8` or `MaybeUninit<u8>`, `Uint8Array` for `[u8; N]`, and `rs.ByteSlice` for `&[u8; N]` |
| `&[T]`, `&mut [T]`, `Box<[T]>` | `rs.Slice<T>`, a range of an array; `rs.ByteSlice` for bytes |
| `Vec<T>` | `rs.Vec<T>`: its items, the array `items`, and the capacity std's holds; `rs.vecFrom(items)` makes one of an array with room for exactly them. `rs.ByteVec` of bytes |
| `Cow<[T]>`, `Cow<str>` | `rs.Cow<T>`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows; `rs.ByteCow` for bytes, an `rs.ByteVec` or an `rs.ByteSlice`; `rs.StrCow` for text, the string it owns or an `rs.Borrowed` holding the one it borrows, which `rs.strCowStr` reads |
| `&T`, `&mut T`, `Box<T>`, `Reverse<T>`, `NonZero<T>`, `AssertUnwindSafe<T>` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, a `String`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rs.Place<T>`, which for what a box holds is the place holding the box |
| `Cow<[T]>`, `Cow<str>` | `rs.Cow<T>`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows; `rs.ByteCow` for bytes, an `rs.ByteVec` or an `rs.ByteSlice`; `rs.StrCow` for text, the `rs.String` it owns or an `rs.Borrowed` holding the string it borrows, whose text `rs.strCowStr` reads |
| `&T`, `&mut T`, `Box<T>`, `Reverse<T>`, `NonZero<T>`, `AssertUnwindSafe<T>` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rs.Place<T>`, which for what a box holds is the place holding the box |
| `*const T`, `*mut T`, `NonNull<T>` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place<T>` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, or copy the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place |
| `MaybeUninit<T>` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined` |
| `ManuallyDrop<T>` | `T`, which nothing drops but its own `drop`; its `Debug`, comparisons, and constants are refused |
Expand Down Expand Up @@ -256,7 +257,8 @@ invalid sequence, as std's does, and borrows the text of valid UTF-8, as std's d
where `str::from_utf8` fails, so each `Utf8Chunk`'s `invalid` bytes are std's, and `Debug`
of it writes what is left as std's does, each invalid byte as `\xNN`. `from_utf8_unchecked`,
of a `str` or a `String`, decodes as `from_utf8_lossy` does the UTF-8 its contract promises.
`String::into_bytes` and `Vec::<u8>::from` a string encode its UTF-8 anew.
`String::into_bytes` and `Vec::<u8>::from` of one encode its UTF-8 into storage of its
capacity, and `Vec::<u8>::from` a `&str` into exactly its bytes.

An `f64` is a JS number, so its operators are JS's, which round as Rust's do. Where
JS differs from std, the runtime has std's: `round` halves away from zero, `max` and
Expand Down Expand Up @@ -413,23 +415,31 @@ closure, which it calls on each char it reads, in the order std reads them, and
returns, or with the iterator it returns, as `split` does, leaving one it borrows to its owner. An offset it
returns, from `rfind` or `match_indices`, counts UTF-8 bytes, as Rust's does, and so does
one `&s[a..b]` or `&s[a..=b]` takes, which reads the string up to it and panics as std
does inside a char, where `get` gives `None`. A JS string can't change in place, so `String`'s edits, `push_str`, `truncate`,
`insert`, `retain`, `replace_range`, and the rest, assign the edited one to the local or
field the `&mut` borrows, taking and checking byte offsets as std does, and `retain` then
drops its closure. `+`, of a `String`, is JS's `+` of the moved text, as `+=` is, and of a
`Cow<str>` takes either side whole when the other is empty, as std's does. A `&mut str` is
the `rs.Place` of its `String` wherever that is, and a part of one, from `&mut s[a..b]`,
does inside a char, where `get` gives `None`. A `String` is an object, so a `&mut` to one
is that object, and its edits, `push_str`, `truncate`, `insert`, `retain`,
`replace_range`, and the rest, change it in place, taking and checking byte offsets as
std does, and `retain` then drops its closure. Each grows its capacity as `RawVec` grows
a `Vec<u8>`'s, by `reserve` of what it adds, or by `push` of each char where std's pushes
each, and `with_capacity`, `reserve`, `reserve_exact`, `shrink_to`, and their `try_` forms
are std's, so `capacity` reads std's. What makes a new one gives it std's room: exactly
its bytes from `to_owned`, `clone`, `join`, and `to_string` of text, a `char`, a `bool`,
or an unsigned integer; a signed integer's most digits and its sign; `format!`'s
estimate, and growth by each write from none for any other `to_string`; the first
item's of a `collect` of `String`s; and std's own reserve and pushes for `replace`,
`to_lowercase`, `from_utf16`, `from_utf8_lossy`, and the rest. `+`, of a `String`, pushes
onto the moved one, as `+=` does, and of a `Cow<str>` takes either side whole when the
other is empty, as std's does. A `&mut str` of a `String` is the `String`, an `rs.Place`
of its text, and a part of one, from `&mut s[a..b]`,
`get_mut`, or `split_at_mut`, is `rs.strPart(p, a, b)`, a place of its own that writes the
whole string anew, checking its offsets as `&s[a..b]` does. `as_bytes_mut`,
`get_unchecked_mut`, and `str::from_utf8_mut` are refused. `extend` and
`collect` read their iterator by `for_each`, as std's do, and of chars first reserve its
size hint's lower bound, which panics past `isize::MAX`. A `drain` holds the `&mut`'s
place and writes the string without the drained text as it drops, so one that
`mem::forget` leaks leaves it as it was, as std's does. A `String` keeps no capacity:
`with_capacity`, `reserve`, and `shrink_to_fit` do nothing, and `capacity` is refused. A `Cow` keeps its
`mem::forget` leaks leaves it as it was, as std's does. A `Cow` keeps its
variant in its class, so a `match` reads it, and `to_mut` of a borrowed one stores what it
owns in the `Cow`'s place before lending it: a `Cow<[T]>`'s clone of its slice, or a
`Cow<str>`'s text, which a `String` is, so the `&mut` borrows the `Cow`'s own place. A
`Cow<str>`'s `String` of its text, which the `&mut` is. A
`Cow<[T]>`'s `clone_from` of an owned one over an owned one clones into the `Vec` it owns,
keeping its buffer, as std's does. `Cow::is_borrowed` and `is_owned` are unstable, and
refused.
Expand Down Expand Up @@ -484,7 +494,7 @@ one kept other than in a local, counts as a write into what it points at. A writ
not change in place what it passes to or gets from the translation either.

A `&mut` to a value JS copies, which no JS value can point at, such as a number, an
`Option`, an enum, a tuple, a `Box`, or a `String`, is an `rs.Place` whose `v` reads and
`Option`, an enum, a tuple, or a `Box`, is an `rs.Place` whose `v` reads and
writes where it points. Where it is only read and written through, in the function that
borrows it, it is the place it borrows, so a body that passes none on makes none. Passed
on, returned, kept in a field, or captured by a value, it is
Expand Down Expand Up @@ -709,11 +719,8 @@ of them:
`Clone`, `Deref`, `AsRef`, `PartialEq` with bytes, `Default`, `Debug`, and `From`
between it and `Vec<u8>`, `String`, `&'static [u8]`, and `&'static str` map to
`rs/bytes.ts`, panicking as `bytes` does, and so do `split_to`, `split_off`,
`truncate`, `clear`, and `slice_ref`, whose panic names no addresses. A `String`'s
bytes are in storage of its capacity, which JS keeps none of, so where any crate makes a
`Bytes` or a `Vec<u8>` of them, every crate is refused what reads a `Bytes`' capacity:
`is_unique`, `try_into_mut`, `truncate`, `clear`, `From` into a `BytesMut` or a
`Vec<u8>`, and `put` of a `Buf` but a `BytesMut` or a slice into a `BytesMut`. `slice_ref` of
`truncate`, `clear`, and `slice_ref`, whose panic names no addresses. A `Bytes` of a
`String` takes storage of its capacity, as `bytes` does. `slice_ref` of
bytes from other storage throws, since Rust may share what JS copied, as a `&'static
str`'s bytes that `as_bytes` encodes anew. `Bytes::from_owner`
reads its owner's bytes once, as `bytes` does, and is refused for an owner that drops,
Expand Down Expand Up @@ -912,13 +919,17 @@ they need.
the `Some` can already be `undefined`: there it is `rs.None`, as `Pending` is
`rs.Pending`, chosen by the type alone, so every function agrees on it. A `rs.None`
for every `Option` would cost callers `?.` and `??` everywhere for the few that need it.
- A `Cow<str>` is a JS string, as `str` and `String` are, rather than an object tagged by
its variant: no `Cow<str>` in moq-net or kio tells its variants apart, and a tag would
wrap every value and unwrap it at every read.
- A `Cow<[T]>` is told apart by class, an owned one bare and a borrowed one in an
- A `String` is an `rs.String`, its text as a JS string beside its UTF-8 length and
capacity, rather than a bare JS string, which holds no capacity, or UTF-8 bytes, which
every `Display` and JS boundary would decode. Measured on
[#574](https://github.com/moq-dev/rust2/pull/574), it matched or beat a JS string at
encoding, decoding, and joining, and cost 7-22% writing lines and up to 2.3x in a map
keyed by one, where UTF-8 bytes cost 5.6-9.8x. A `&str` and a `Box<str>` stay JS
strings, which hold all they do.
- A `Cow` is told apart by class, an owned one bare and a borrowed one in an
`rs.Borrowed`, so it clones and drops as std's does and only the borrowed side pays a
wrapper. Untagged, as `Cow<str>` is, `to_mut` would clone on every call and a `match`
would be refused; tagged, every `Cow` would be wrapped.
wrapper. Untagged, `to_mut` would clone on every call and a `match` would be refused;
tagged, every `Cow` would be wrapped.
- A `Duration` keeps std's fields, whole seconds and nanoseconds, rather than one count
of nanoseconds, so its overflow, `checked_*`, and ordering are std's exactly, and a
lowering of its methods from core's MIR would read the same fields.
Expand Down
5 changes: 3 additions & 2 deletions ts/bench/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ function render(): string {
`- moq: ${text(["git", "-C", checkout, "rev-parse", "HEAD"])}`,
...engines.map((e) => `- ${e.engine}`),
"",
"Each cell is nanoseconds per item (a varint, a frame header, a message, a byte iterated, a key, a timestamp converted, added, or compared, a line formatted, or a step): the median",
"Each cell is nanoseconds per item (a varint, a frame header, a message, a byte iterated, a key, a timestamp converted, added, or compared, a line formatted, a step, or a string coded, joined, or keyed): the median",
`of ${rounds} samples of at least ${sampleMs} ms, ± half their interquartile range. Each round runs`,
`the JS routes and workloads in a new random order, from seed ${seed}. \`native\` is a release`,
"build on the host, its workloads in a fixed order. `wasm` is the same",
Expand All @@ -368,7 +368,8 @@ function render(): string {
"`js/net`, called as its own benchmarks call it, for `iter/` the same chain with ES",
"iterator helpers (`bench/iter.ts`), for `btree/` a sorted array (`bench/sorted.ts`), for `hash/` a `Map` (`bench/hashed.ts`), and",
"for `time/` conversions `bigint`, as moq's `js/hang` converts timescales (`bench/rescale.ts`),",
"for `time/checked-add` and `time/ord` moq's own `Timestamp` (`bench/timestamp.ts`), for `fmt/` a template literal (`bench/fmt.ts`), and for `parts/` a plain array (`bench/parts.ts`).",
"for `time/checked-add` and `time/ord` moq's own `Timestamp` (`bench/timestamp.ts`), for `fmt/` a template literal (`bench/fmt.ts`), for `parts/` a plain array (`bench/parts.ts`), and for `str/`",
"moq's Writer and Cursor, a template literal, and a `Map` (`bench/strings.ts`).",
"Each JS route and `wasm` runs alone in a worker of its own, a fresh one each round. `A/A` is the translation again: its",
"gap from `translated` is the noise. A `reads` decode is the frame headers above as they",
"arrive that many bytes at a time, so a header cut by a read comes up short and is",
Expand Down
9 changes: 9 additions & 0 deletions ts/bench/moq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { order } from "./order.ts";
import { hashed } from "./hashed.ts";
import { packets } from "./packets.ts";
import { parts } from "./parts.ts";
import { decodeStrings, encodeStrings, join, keyed } from "./strings.ts";
import { push } from "./push.ts";
import { rescale } from "./rescale.ts";
import { ordered } from "./sorted.ts";
Expand Down Expand Up @@ -77,6 +78,14 @@ export function run(input: Uint8Array): Uint8Array | Promise<Uint8Array> {
return format(data);
case 17:
return parts(selector, data);
case 18:
return encodeStrings(selector, data, Lite.DRAFT_06);
case 19:
return decodeStrings(data, Lite.DRAFT_06);
case 20:
return join(selector, data);
case 21:
return keyed(selector, data);
default:
throw new Error(`unknown workload ${op}`);
}
Expand Down
Loading