@@ -18,16 +18,20 @@ use crate::crypto::ID_LEN;
1818/// so the Data Plane can address a specific stored version directly — but that
1919/// is an internal delivery detail, never a separate user-facing kind of route.
2020///
21- /// Backed by an `Arc<str>` rather than a `String`. The Data Plane allocates the
22- /// id exactly once (at [`parse`](Self::parse)); the cache must then own the key
23- /// for longer than the request, and the loader closure needs its own copy. With
24- /// shared ownership those follow-on copies — `clone` for the closure and moka's
25- /// internal key clone on insert — are atomic refcount bumps, never fresh 64-byte
26- /// heap allocations. Borrowing stays a plain, safe deref via [`as_str`].
21+ /// Backed by a fixed-capacity, inline [`ArrayString<ID_LEN>`] rather than a
22+ /// heap `String` — the type *is* "a validated string of exactly [`ID_LEN`]
23+ /// characters". This makes the id `Copy` and keeps it entirely off the
24+ /// allocator on the Data Plane hot path: `parse` writes the bytes into the
25+ /// stack buffer, the loader-closure copy and moka's internal key clone are
26+ /// 64-byte `memcpy`s (no refcount, no allocation), and the cache stores the key
27+ /// inline in its node instead of in a separate per-entry heap allocation.
28+ /// `ArrayString` preserves its UTF-8 invariant internally, so [`as_str`] stays
29+ /// a safe, zero-cost borrow with no `unsafe` and no re-validation.
2730///
31+ /// [`ArrayString<ID_LEN>`]: arrayvec::ArrayString
2832/// [`as_str`]: Self::as_str
29- #[ derive( Debug , Clone , PartialEq , Eq , Hash ) ]
30- pub struct RouteId ( std :: sync :: Arc < str > ) ;
33+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash ) ]
34+ pub struct RouteId ( arrayvec :: ArrayString < ID_LEN > ) ;
3135
3236/// Error returned when a candidate route id fails validation.
3337#[ derive( Debug , thiserror:: Error ) ]
@@ -41,9 +45,10 @@ pub enum RouteIdError {
4145impl RouteId {
4246 /// Parse and validate an untrusted id (e.g. from a request path).
4347 ///
44- /// The structural checks (length, then charset) run before the id is
45- /// materialized, so malformed input is rejected without any allocation;
46- /// only a well-formed id is interned into the single shared `Arc<str>`.
48+ /// The structural checks (length, then charset) run first, so malformed
49+ /// input is rejected up front; a well-formed id is then copied into the
50+ /// inline buffer without ever touching the heap. The length check
51+ /// guarantees the id fits the fixed [`ID_LEN`] capacity exactly.
4752 pub fn parse ( raw : & str ) -> Result < Self , RouteIdError > {
4853 if raw. len ( ) != ID_LEN {
4954 return Err ( RouteIdError :: WrongLength ( raw. len ( ) ) ) ;
@@ -54,7 +59,10 @@ impl RouteId {
5459 {
5560 return Err ( RouteIdError :: InvalidCharacter ) ;
5661 }
57- Ok ( Self ( std:: sync:: Arc :: from ( raw) ) )
62+ // Infallible: `raw.len() == ID_LEN` equals the buffer capacity.
63+ Ok ( Self (
64+ arrayvec:: ArrayString :: from ( raw) . expect ( "len == ID_LEN" ) ,
65+ ) )
5866 }
5967
6068 /// Adopt a freshly-minted, already-signed id from [`crypto::IdSigner`].
@@ -64,26 +72,26 @@ impl RouteId {
6472 #[ must_use]
6573 pub fn from_signed ( id : String ) -> Self {
6674 debug_assert_eq ! ( id. len( ) , ID_LEN , "signer must emit {ID_LEN}-char ids" ) ;
67- Self ( std :: sync :: Arc :: from ( id ) )
75+ Self ( arrayvec :: ArrayString :: from ( & id ) . expect ( "signer emits ID_LEN-char ids" ) )
6876 }
6977
7078 /// Borrow the id as a string slice.
7179 #[ must_use]
7280 pub fn as_str ( & self ) -> & str {
73- & self . 0
81+ self . 0 . as_str ( )
7482 }
7583
76- /// Consume the newtype, yielding an owned string. Used only on cold Control
77- /// Plane response paths, never on the Data Plane hot path.
84+ /// Yield an owned string. Used only on cold Control Plane response paths,
85+ /// never on the Data Plane hot path.
7886 #[ must_use]
7987 pub fn into_inner ( self ) -> String {
80- self . 0 . as_ref ( ) . to_owned ( )
88+ self . 0 . as_str ( ) . to_owned ( )
8189 }
8290}
8391
8492impl std:: fmt:: Display for RouteId {
8593 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
86- f. write_str ( & self . 0 )
94+ f. write_str ( self . 0 . as_str ( ) )
8795 }
8896}
8997
0 commit comments