pub struct WstringLengthPolicy {
pub counts: WstringLengthCounts,
pub terminator: WstringTerminator,
pub unit: WstringUnitWidth,
}Expand description
How a wstring’s length is written and how wide its code units are
— C1 × C2 × C3, a product space and not a list.
§Why this is a struct and no longer an enum
It was an enum of five named forms, and the enum was the defect. The
five names mixed three independent questions — what the length
counts, whether a terminator is included, how wide a code unit is —
and a sixth question, byte order, which had no business here at all
and has moved to WstringUnitOrder.
A list of named forms can only hold the combinations somebody has
already written down. Measured 2026-08-12 by reconstruction, over
wstring-unbounded-utf8, wstring-bounded-utf8,
wstring-unbounded-empty and array-1d-wstring: every combination
of (C1, C2, C3, C4) was encoded and compared against the recorded
octets, and exactly one reproduces each row.
| implementation | C1 counts | C2 terminator | C3 unit | C4 order |
|---|---|---|---|---|
| Cyclone | octets | absent | 2 | host, unswapped |
| OpenDDS | octets | absent | 2 | stream |
| Fast DDS | characters | absent | 2 | stream |
| RTI, XCDR1 | characters | present | 4 | stream |
| ros-cyclone | characters | absent | 4 | stream |
| ros-fastrtps | characters | absent | 4 | host, unswapped |
| omniORB | octets, mark counted | absent | 2 | byte-order mark |
| TAO | octets | absent | 4 | stream |
| JacORB | octets | absent | 2 | big-endian always |
The row that forced the rebuild is either ROS one: characters,
no terminator, four-octet units. The old enum could not reach it.
CharacterCount was that length with two-octet units and
SpecificationWideUnits was four-octet units under the other
length; the cell between them had no name, so two implementations
wrote a form this project had no way to express. That is what an
enumeration standing in for a product space costs, and
Self::ROS_TYPE_SUPPORT is the cell.
tests::every_cell_of_the_product_is_reachable fails if any of
the eight cells stops being constructible or stops round-tripping
through Self::parse.
§Position
Every method here takes a WirePosition, for the reason
WcharFraming’s methods do: a caller must not be able to reach a
member’s answer for an element by not asking. On this axis the two
positions are measured to agree — all nine implementations
answer the member and the collection element identically, checked
2026-08-12 and guarded by
wstring_length_policy.rs::the_two_positions_still_agree. That is a
checked non-existence, not an assumption, and the argument is here so
that the day an implementation parts them there is a place to put it
rather than a signature to change.
§Width, offsets, and the scalar wchar
RTI’s four-octet XCDR1 wchar is the same four-octet character its
XCDR1 wstring writes, and its two-octet XCDR2 wchar the same as
its XCDR2 wstring. So the scalar takes C3 and there is no second
setting: a policy saying “four-octet wstring characters, two-octet
wchar” would name no implementation.
This is why LayoutKey::offset_key carries it. A wstring plans
as crate::Extent::Unbounded under every cell and moves no
offset, but a wchar is a sized primitive: at four octets it aligns
to four and shifts every member behind it. A cache that served a
two-octet plan to a four-octet request would put them all in the
wrong place.
A code point above the BMP has no wchar in any cell
(§7.2.2.2.1.2.5) — the four-octet form zero-extends a code unit,
it does not carry a code point.
Fields§
§counts: WstringLengthCountsC1 — what the length prefix counts.
terminator: WstringTerminatorC2 — whether a terminating NUL is written and counted.
unit: WstringUnitWidthC3 — how wide one code unit is.
Implementations§
Source§impl WstringLengthPolicy
impl WstringLengthPolicy
Sourcepub const SPECIFICATION: Self
pub const SPECIFICATION: Self
Octets, no terminator, two-octet units.
XTypes 1.3 §7.4.1.1.2 and CORBA’s GIOP 1.2 wstring over a
two-octet code set. The default. Cyclone and OpenDDS under both
versions, RTI under XCDR2, omniORB and JacORB.
Sourcepub const CHARACTER_COUNT: Self
pub const CHARACTER_COUNT: Self
Characters, no terminator, two-octet units.
What Fast DDS writes under both versions. The writer refuses a code point outside the BMP in this cell rather than truncating it the way Fast CDR does: the truncation is undetectable at the far end, so emitting it would be this project putting a different character on the wire and calling it interoperability.
Sourcepub const CHARACTER_COUNT_TERMINATED: Self
pub const CHARACTER_COUNT_TERMINATED: Self
Characters including the terminator, four-octet units.
CORBA 3.3 part 2 §9.3.1.6’s GIOP 1.1 wstring over a
four-octet code set, and what RTI Connext 7.7 writes under
XCDR1 — measured 2026-08-12 at both the member and the
collection-element position, byte for byte, with no
RTI-specific rule anywhere. Its XCDR2 output is
Self::SPECIFICATION.
Sourcepub const SPECIFICATION_WIDE_UNITS: Self
pub const SPECIFICATION_WIDE_UNITS: Self
Octets, no terminator, four-octet units.
CORBA §9.3.1.6’s GIOP 1.2 wstring over a four-octet
transmission code set — TAO 2.5.24.
Sourcepub const ROS_TYPE_SUPPORT: Self
pub const ROS_TYPE_SUPPORT: Self
Characters, no terminator, four-octet units.
The cell the old enumeration could not reach. Both ROS 2
type supports write it: rosidl_typesupport_fastrtps counts
characters like Fast CDR and hands each one over as a
four-octet wchar_t, and rmw_cyclonedds_cpp walking the
introspection type support arrives at the same octets.
Measured 2026-08-12 from the recorded corpus. wstring-unbounded-utf8,
plain-cdr1 little endian, "äöü ሴ Z":
ros-fastrtps 07000000 e4000000 f6000000 fc000000 20000000 34120000 20000000 5a000000
ros-cyclone 07000000 e4000000 f6000000 fc000000 20000000 34120000 20000000 5a000000Seven, for seven characters, over four-octet units and no terminator. The two are byte-identical here, and they part only on C4 in a big-endian stream.
Sourcepub const MEASURED: [Self; 5]
pub const MEASURED: [Self; 5]
The cells an implementation has been measured in, for a caller that wants to sweep the named forms.
Not the set of legal values — that is the eight-cell product,
and Self::ALL_CELLS enumerates it. A caller that sweeps this
list is sweeping what has been seen, which is the right set for a
vendor comparison and the wrong one for a completeness check.
Sourcepub const ALL_CELLS: [Self; 8]
pub const ALL_CELLS: [Self; 8]
Every cell of C1 × C2 × C3, measured or not.
Eight, and the three that no implementation writes are as
constructible and as nameable as the five that are. A
completeness check runs over this; a vendor sweep runs over
Self::MEASURED.
Sourcepub const COORDINATE_FORM_HELP: &'static str = "octets|characters / unterminated|terminated / 2|4"
pub const COORDINATE_FORM_HELP: &'static str = "octets|characters / unterminated|terminated / 2|4"
The coordinate form’s grammar, for a help text next to
Self::MEASURED’s five named cells.
Kept here, beside Self::parse, because a caller building a
help text out of the named cells alone would leave the three
unnamed ones unreachable from anything it printed — the defect
Self::ALL_CELLS’s own documentation describes for the old
enum. One constant next to the grammar it describes, rather than
a copy at each call site that could drift from what
Self::parse actually accepts.
Sourcepub const fn new(
counts: WstringLengthCounts,
terminator: WstringTerminator,
unit: WstringUnitWidth,
) -> Self
pub const fn new( counts: WstringLengthCounts, terminator: WstringTerminator, unit: WstringUnitWidth, ) -> Self
Build a cell of the product directly.
Public and const so that a cell nobody has named is as easy to
reach as one that is named. The named constants below are
conveniences for the cells an implementation was measured in;
they are not the set of legal values.
Sourcepub const fn is_default(self) -> bool
pub const fn is_default(self) -> bool
Whether this is the default.
Sourcepub const fn as_str(self) -> &'static str
pub const fn as_str(self) -> &'static str
The name a configuration uses.
Every one of the eight cells has one, and every one round-trips
through Self::parse. A cell an implementation was measured in
renders under the name it shipped with; the three nobody writes
render as their coordinates, counts/terminator/width. That a
cell nobody has named is still writable on a command line and
still readable in a diagnostic is the property the enumeration
did not have, and it is why the ROS form was unreachable for as
long as the names were the values.
Sourcepub fn parse(value: &str) -> Option<Self>
pub fn parse(value: &str) -> Option<Self>
Parse a configuration value.
A named cell, or the three coordinates separated by /. Every
value Self::as_str produces parses back to the same cell, and
the coordinate form reaches all eight whether or not they carry a
name. An unknown value returns None rather than falling back to
the default: a typo in a wire-affecting setting must be loud.
Sourcepub const fn counts_at(self, _position: WirePosition) -> WstringLengthCounts
pub const fn counts_at(self, _position: WirePosition) -> WstringLengthCounts
C1 at a position — what the length prefix counts.
An accessor and not the field, for the reason the type’s
documentation gives: a caller that reads policy.counts
directly has answered for every position by not asking about
any, which is exactly the shape that let a member’s wchar
framing stand in for an element’s through five emitters. The
nine implementations answer both positions alike, measured
2026-08-12, so the argument is unused — and it is the argument
that keeps that a measurement rather than an assumption.
Sourcepub const fn terminator_at(self, _position: WirePosition) -> WstringTerminator
pub const fn terminator_at(self, _position: WirePosition) -> WstringTerminator
C2 at a position — whether a terminating NUL is written.
Sourcepub const fn unit_at(self, _position: WirePosition) -> WstringUnitWidth
pub const fn unit_at(self, _position: WirePosition) -> WstringUnitWidth
C3 at a position — how wide one code unit is.
Sourcepub const fn character_width(self, _position: WirePosition) -> usize
pub const fn character_width(self, _position: WirePosition) -> usize
How many octets one Char16 takes, in a wstring and as a
scalar wchar alike.
Takes the position for the reason the type’s documentation
gives. The two positions are measured to agree, so the argument
is unused in the body and named _position rather than dropped
from the signature: a caller still cannot reach this without
saying where it is asking from.
Sourcepub const fn char16_octets(self, _position: WirePosition) -> u8
pub const fn char16_octets(self, _position: WirePosition) -> u8
The same width as a byte count, for the one caller that needs it
narrow: WcharFraming::scalar_layout, whose alignment answer
has to be a u8 and must not be produced by a fallible
conversion.
Sourcepub const fn char16_size(self, _position: WirePosition) -> u32
pub const fn char16_size(self, _position: WirePosition) -> u32
The same width as the planner wants it.
A separate signature and not a cast at the call site: a plan that
narrowed a usize into a u32 with a fallback would answer 2
for a width it did not recognise, and a wrong width is a wrong
offset for every member behind the member.