wtf-string 0.1.0

OsString-shaped strings with native, conversion-free u16 (WTF-16) storage for Windows FFI.
Documentation
# Completed checklist: wtf-string

Append-only archive of completed milestones moved out of [CHECKLIST.md](CHECKLIST.md).

## Moved 2026-08-19 -- M1 scaffold+design, M2 encoding+core types, M3 conversions/formatting/comparison

### M1 -- Crate scaffold and design record

- [x] **M1.1** -- Scaffold `crates/wtf-string`: portable [Cargo.toml]Cargo.toml (no deps, `edition`/`rust-version`/
  `license` from the workspace), [src/lib.rs]src/lib.rs crate-doc skeleton, and add the crate to the workspace members.

- [x] **M1.2** -- Seed Tier-1 [DESIGN-NOTES.md]DESIGN-NOTES.md, Tier-2 [DESIGN-RATIONALE.md]DESIGN-RATIONALE.md,
  and the Tier-3 session (D-1...D-13); register the crate in the master and crate-local plans. CI covers it
  automatically via the workspace build.

### M2 -- Encoding seam and core types

- [x] **M2.1** -- `WtfEncoding` trait (associated `Unit`, the `NUL` terminator unit) and the `Wtf16`
  encoding (`Unit = u16`) (D-2/D-3).

- [x] **M2.2** -- `WtfString<E>` (owns `Vec<E::Unit>`) and `WtfStr<E>` (`#[repr(transparent)]` over
  `[E::Unit]`): the **always-terminated** storage invariant (hidden trailing `0x0000`; `len()`/spans exclude
  it), construction (`new`, `from_units`), content access on `WtfStr` (`as_units`, `len`, `is_empty`,
  `has_interior_nul`), the `Wtf16String`/`Wtf16Str` aliases, and std parity
  (`Deref`/`AsRef`/`Borrow`/`ToOwned`/`Default`/`Clone`). The FFI pointer surface is M4. (The old M2.2/M2.3
  are merged: the owned type exposes content access *through* `Deref`, so the type definitions and the
  std-parity plumbing cannot compile independently.) (D-4/D-6/D-7)

- [x] **M2.3** -- Tests (sibling [tests.rs]src/string/tests.rs): the terminator invariant survives construction/clone; spans
  never include the terminator; interior-NUL content is preserved and `has_interior_nul()` reports it;
  `Deref`/`ToOwned` round-trip; empty and large inputs.

### M3 -- str/String conversions and comparison

- [x] **M3.1** -- Boundary conversions: `From<&str>` / `From<String>` (encode once), `into_string() -> Result`
  and `to_string_checked() -> Option` (fallible exact -- `to_string` is avoided since it would collide with
  `ToString` once `Display` lands in M3.2), and `to_string_lossy()` (U+FFFD) (D-8).

- [x] **M3.2** -- `Display` (lossy) and `OsStr`-style escaped `Debug`.

- [x] **M3.3** -- `Ord` / `PartialOrd` / `Eq` / `PartialEq` / `Hash` (binary over units), and cross-type
  comparison with `&str` where it is unambiguous.

- [x] **M3.4** -- Property tests: round-trips through `str`/`String`, ill-formed surrogates preserved in
  storage and replaced by lossy conversion, `> MAX_PATH` lengths, mixed BMP/astral content.

## Moved 2026-08-19 -- M4 FFI surface

### M4 -- FFI surface

- [x] **M4.1** -- Counted access (`as_ptr` + `len`) and the terminated `LPCWSTR` pointer accessor, documented
  as valid only when `has_interior_nul()` is false (D-7/D-10).

- [x] **M4.2** -- Output constructors: `with_capacity` (overflow-guarded), `as_mut_ptr`, and
  `unsafe set_len_from_ffi(content_units)` (explicit content-length convention that appends the terminator,
  never inspecting the buffer) for caller-allocated buffer-fill APIs (D-9).

- [x] **M4.3** -- `unsafe from_wide_ptr(ptr, len)` for callee-allocated buffers, with an explicit safety
  contract (ownership, count semantics, no reference retained; `len == 0` never dereferences `ptr`) (D-9).

- [x] **M4.4** -- Tests: mock buffer-fill (excludes-NUL, includes-NUL via content length, and a trailing
  content-NUL preserved verbatim) rebuilds the invariant; `from_wide_ptr` copies losslessly and is safe at
  `len == 0`; terminated pointer round-trips through a `from_wide_ptr`.

## Moved 2026-08-20 -- M5 Windows OsStr/OsString interop

### M5 -- Windows OsStr/OsString interop

- [x] **M5.1** -- `cfg(windows)` lossless bridge: `from_os_str` / `to_os_string`, `from_wide` / `encode_wide`
  vocabulary aliases, and `From`/`Into` conversions between `OsStr`/`OsString` and `Wtf16Str`/`Wtf16String`.
  Conversion-based both ways (lossless, incl. unpaired surrogates); no borrowing `AsRef<OsStr>` because the
  backing widths differ (D-5/D-8/D-14).

- [x] **M5.2** -- Integration test (Windows): lossless `OsStr` -> `Wtf16String` -> `OsString` round-trips
  including unpaired surrogates and interior NULs (a ~400-case bulk sweep), a real wide `lstrlenW` call fed
  from `as_terminated_ptr()`, and a counted `as_ptr` hand-off matching `encode_wide` -- all with no
  conversion.

  > **-> CROSS-COMPONENT HANDOFF:** next work is in component `crates/windows-file-watcher` -> `M8` ->
  > `M8.1` (`Migrate RelativeName to Wtf16Str/Wtf16String`). See
  > [../windows-file-watcher/CHECKLIST.md]../windows-file-watcher/CHECKLIST.md.

## Moved 2026-08-20 -- M6 The Wtf8 encoding arm

### M6 -- The `Wtf8` encoding arm

- [x] **M6.1** -- The `Wtf8` encoding arm (`WtfString<Wtf8>` / `WtfStr<Wtf8>`): implement the crate-owned
  `WtfEncoding` contract for `u8`/WTF-8 units -- `Unit = u8`; storage is arbitrary WTF-8 (ill-formed-tolerant,
  matching `OsStr`); `encode_str` is the identity on a UTF-8 `str`'s bytes; exact decode is
  valid-UTF-8-or-`None`; lossy decode replaces with U+FFFD; comparison/hash are binary over bytes; `debug_fmt`
  escapes ill-formed sequences losslessly. Storage is a crate-owned `Vec<u8>` whose WTF-8 layout matches
  `OsString`'s but is not built on it, giving a uniform API across storage widths (D-3).

- [x] **M6.2** -- Tests: extend the corpus/property matrix over the `Wtf8` arm (round-trips, ill-formed WTF-8
  preserved in storage and lossily replaced, interior NUL, boundary scalars), and assert cross-width parity
  with `Wtf16` for the shared encoding-generic API. Cross-width parity asserts shared invariants (checked-decode
  failure, full U+FFFD replacement) rather than raw lossy-string equality, since `String::from_utf8_lossy` is
  byte-granular while `String::from_utf16_lossy` is unit-granular (D-15).

## Moved 2026-08-20 -- M7 Safe mutation surface (OsString parity)

### M7 -- Safe mutation surface (`OsString` parity)

- [x] **M7.1** -- `WtfString<E>::push`/`push_str`, `clear`, and capacity management (`capacity`, `reserve`,
  `reserve_exact`, `shrink_to_fit`, `shrink_to`), matching `OsString`'s actual (narrow) public surface --
  `OsString` has no `truncate`/`pop`/indexed edit, since arbitrary truncation could split a multi-byte
  WTF-8/surrogate sequence into ill-formed content. Each mutating op re-establishes the always-present
  terminator (D-7/D-16).

- [x] **M7.2** -- Tests: matrix/property coverage over both widths -- `push`/`push_str` growing content and
  preserving any existing interior NUL, `clear` then re-`push` round-trips, capacity/reserve behaving as
  documented, and the terminator invariant holding after every mutating op.

## Moved 2026-08-20 -- M8 windows-crate Param<PCWSTR> interop

### M8 -- `windows`-crate `Param<PCWSTR>` interop

- [x] **M8.1** -- Optional, feature-gated `windows`-crate `Param<PCWSTR>` impl so the high-level `windows`
  crate accepts our type directly, without imposing a hard dependency when the feature is off (D-10).
  Implemented as `impl Param<PCWSTR> for &Wtf16String` behind the off-by-default `windows-core` feature,
  handing over `as_terminated_ptr()` unchanged. No impl for `&Wtf16Str` (no terminator on a borrowed slice,
  D-7). The default build remains zero-dependency (D-11). Constraints recorded as D-17: the seam is pinned to
  one `windows-core` version, and it binds to that crate's `#[doc(hidden)]` `Param` machinery because
  implementing the trait is the only way to satisfy the bound.

- [x] **M8.2** -- Tests (Windows, feature-gated): a `Param<PCWSTR>`-bound call fed from our terminated pointer
  with no conversion. Asserts the bound is satisfied (it would not compile otherwise), that the callee receives
  the string's own terminated pointer, that the pointer walks back as a C string, that a real `lstrlenW` call
  agrees with our length, and that an interior NUL truncates exactly as the wrapped pointer's contract says.

## Moved 2026-08-21 -- M9 no_std / alloc-only support

### M9 -- `no_std` / `alloc`-only support

- [x] **M9.1** -- `no_std` / `alloc`-only support: gate the `std`-only surface (the `OsStr`/`OsString` interop)
  behind a default `std` feature, and build the portable core (storage, `str`/`String` conversions, FFI
  pointer surface) on `alloc` alone (D-11). The crate root is unconditionally `#![no_std]` + `extern crate
  alloc`, so the core cannot silently reacquire a `std` dependency; `std` is additionally linked under
  `cfg(test)` (tests are std-only) and `cfg(doc)` (so `std::ffi::OsStr` intra-doc links resolve in an
  alloc-only doc build). The `std` feature forwards to `windows-core`'s own `std` when that optional
  dependency is enabled. Recorded as D-18.

- [x] **M9.2** -- CI: an `alloc`-only (`--no-default-features`) build+test target so the portable core stays
  verified without `std`. The new `alloc-only` job builds and lints the crate for the bare-metal
  `thumbv7em-none-eabi` target -- which ships no `std` at all, so success is real proof of `no_std`-ness,
  unlike `--no-default-features` on a host -- and runs the alloc-only test configuration on the host, where a
  harness exists. Verified the gate is not vacuous: the same target build *fails* at `extern crate std`
  (E0463) when the `std` feature is on.

## Moved 2026-08-21 -- M10 documentation, examples, publication readiness

### M10 -- Documentation, examples, publication readiness

- [x] **M10.1** -- The [README.md]README.md and [lib.rs]src/lib.rs top-level docs: the storage model, the
  conversion-cost contract, the FFI surface, and both encoding widths. Both now carry a conversion-cost table
  (naming which operations are free and which are boundary crossings, and how that mirrors `OsString`), an
  FFI-surface walkthrough split by input convention (counted vs terminated) and output shape (buffer-fill vs
  callee-allocated), the interior-NUL contract, and a runnable example.

- [x] **M10.2** -- Runnable example: a wide Win32 round-trip (input via `as_ptr`, output via a buffer-fill
  constructor) showing the zero-conversion path. [examples/win32_round_trip.rs]examples/win32_round_trip.rs
  exercises terminated input and buffer-fill output against `GetFullPathNameW`, plus counted input against
  `CompareStringOrdinal` (which takes explicit lengths, so it also demonstrates the counted pair working on a
  borrowed slice that carries no terminator). Entry points are declared inline to keep the crate
  dependency-free; verified by running it.

- [x] **M10.3** -- Publication readiness: crate metadata, changelog, and a final review pass over the public
  surface; record the remaining deferred seam (below) as reserved. Verified with `cargo publish --dry-run`
  (packages and verifies cleanly). Seeded [CHANGELOG.md]CHANGELOG.md with the same `# Changelog` header
  release-please prepends to in the already-released crates. The reserved seam -- a checked no-interior-NUL
  C-string companion (D-7, M-inf.1) -- is now recorded in the crate docs so users know the surface may grow
  there.