3.1.0 — typed loops, serialize-only serde, yoke ownership
3.1.0 — typed descriptor loops, Serialize-only serde, owned views
2.0 made individual text fields decode on demand (DvbText). 3.1 finishes the
job for the descriptor loops themselves, settles serde into a one-way
display/export format, types the last raw service loop, and adds an optional
zero-cost way to own a parsed view past its input buffer. Wire parsing stays
byte-identical throughout: this release only changes field types, serialized
shape, and what you can do with a parsed value after the bytes are gone. All four
crates release in lockstep at 3.1.0.
Highlights
The loop walks itself
Every SI descriptor-loop field on a table — SdtService.descriptors,
EitEvent.descriptors, Pmt.program_info, and the rest — is now a zero-copy
DescriptorLoop that walks into typed AnyDescriptors on demand. No more
parse_loop(field) by hand at the call site.
use dvb_si::descriptors::AnyDescriptor;
// service.descriptors is a DescriptorLoop — iterate it directly.
for item in service.descriptors.iter() {
if let Ok(AnyDescriptor::Service(sd)) = item {
println!("{}", sd.service_name.decode()); // DvbText, decoded on demand
}
}
let raw: &[u8] = service.descriptors.raw(); // wire bytes still one call awayparse_loop is unchanged and still available for free byte slices.
Decoded JSON, for free
Serialize a whole table and its descriptor loops decode themselves — the typed
service_name appears inline, no manual walking:
Per-entry parse errors surface as {"parseError": "…"} rather than being
dropped. Variant keys are camelCase (service), struct fields stay snake_case.
serde is Serialize-only, workspace-wide
JSON is a display/export format only. Every Deserialize derive and impl is
removed across dvb-si, dvb-t2mi, dvb-bbframe, and dvb-common (including
the manual text::LangCode impl and the now-dead serde(borrow) /
serde(bound(deserialize = …)) attributes). Parsing FROM JSON is deliberately
unsupported — to reconstruct a value, re-parse the wire bytes. Serialize
output is byte-for-byte unchanged.
let json = serde_json::to_string(&sdt)?; // Serialize: unchanged
let sdt = Sdt::parse(§ion_bytes)?; // reconstruct via Parse, not DeserializeThe last raw loop gets typed: SIT services
The SIT per-service loop is now a typed Vec<SitService> — mirroring
SdtService and completing table consistency (EN 300 468 §7.1.2, Table 164).
pub struct SitService<'a> {
pub service_id: u16,
pub running_status: u8, // 3 bits
pub descriptors: DescriptorLoop<'a>,
}Own a parsed view: the yoke feature
Every view borrows its section bytes, which keeps parsing zero-copy but pins the
view to the input buffer's lifetime. Enable the optional yoke feature and a
parsed table becomes a 'static, Send + Sync, cheaply-Clone Owned<T> — no
re-parse, no hand-written mirror type, no lifetime in your own structs.
use std::sync::Arc;
use dvb_common::Parse;
use dvb_si::owned::Owned;
use dvb_si::tables::pmt::Pmt;
// Move the bytes into an Arc cart; parse once; keep the result forever.
let cart: Arc<[u8]> = Arc::from(section_bytes); // source bytes consumed
let owned: Owned<Pmt<'static>> = Owned::try_new(cart, |b| Pmt::parse(b))?;
// 'static + Send + Sync + Clone — store it, cache it, send it across threads.
let program = owned.get().program_number;
let handle = std::thread::spawn(move || owned.get().streams.len());dvb-t2mi and dvb-bbframe ship the same yoke feature on their own view
types. It is purely additive, off by default, and pulls in no dependencies on
default builds.
Breaking changes
| Change | Migration |
|---|---|
SI descriptor-loop fields are DescriptorLoop<'a> instead of &[u8] / Vec<u8> |
Call .iter() to walk, .raw() for wire bytes; Deref<[u8]> still works |
Cat, Tsdt, Sit are now borrowed (<'a>) |
Keep the section bytes alive; add a lifetime to any struct that stores them |
Deserialize dropped workspace-wide — serde is Serialize-only |
Re-parse from wire bytes instead of deserializing from JSON |
| serde JSON: descriptor loops emit typed decoded arrays, not byte arrays | Update JSON expectations; per-entry errors become {"parseError": …} |
SIT service loop is typed: Sit.services: Vec<SitService> replaces the raw service_loop |
Iterate sit.services; each entry carries a typed descriptors loop |
Full details and before/after code for every break:
MIGRATION-3.1.md
Links
All four crates released in lockstep at 3.1.0.