dsi_bitstream/dispatch/mod.rs
1/*
2 * SPDX-FileCopyrightText: 2025 Tommaso Fontana
3 * SPDX-FileCopyrightText: 2025 Inria
4 * SPDX-FileCopyrightText: 2025 Sebastiano Vigna
5 *
6 * SPDX-License-Identifier: Apache-2.0 OR MIT
7 */
8
9//! Programmable static and dynamic dispatch for codes.
10//!
11//! The modules in [codes], such as [`omega`], extend [`BitRead`] and
12//! [`BitWrite`] to provide a way to read and write codes from a bitstream. The
13//! user can thus select at compile time the desired trait and use the
14//! associated codes.
15//!
16//! In many contexts, however, one does not want to commit to a specific set of
17//! codes, but rather would like to write generic methods that accept some code
18//! as an input and then use it to read or write values. For example, a stream
19//! encoder might let the user choose between different codes, depending on the
20//! user's knowledge of the distribution of the values to be encoded.
21//!
22//! Having dynamic selection of a code, however, entails a performance cost, as,
23//! for example, a match statement must be used to select the correct code. To
24//! mitigate this cost, we provide two types of dispatch traits and three types
25//! of implementations based on them.
26//!
27//! # Dispatch Traits
28//!
29//! The traits [`DynamicCodeRead`] and [`DynamicCodeWrite`] are the most generic
30//! ones, and provide a method to read and write a code from a bitstream. By
31//! implementing them, you can write a method accepting one or more unspecified
32//! codes, and operate with them. For example, in this function we read twice a
33//! code and return the sum of the two values, but make no commitment on which
34//! code we will be using:
35//! ```rust
36//! use dsi_bitstream::prelude::*;
37//! use dsi_bitstream::dispatch::{CodesRead, DynamicCodeRead};
38//! use std::fmt::Debug;
39//!
40//! fn read_two_codes_and_sum<
41//! E: Endianness,
42//! R: CodesRead<E> + ?Sized,
43//! GR: DynamicCodeRead
44//! >(
45//! reader: &mut R,
46//! code: GR,
47//! ) -> Result<u64, R::Error> {
48//! Ok(code.read(reader)? + code.read(reader)?)
49//! }
50//! ```
51//! On the other hand, the traits [`StaticCodeRead`] and [`StaticCodeWrite`] are
52//! specialized for a reader or writer of given endianness. This means that they
53//! can in principle be implemented for a specific code by storing a function
54//! pointer, with much less runtime overhead.
55//! ```rust
56//! use dsi_bitstream::prelude::*;
57//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead};
58//! use std::fmt::Debug;
59//!
60//! fn read_two_codes_and_sum<
61//! E: Endianness,
62//! R: CodesRead<E> + ?Sized,
63//! SR: StaticCodeRead<E, R>
64//! >(
65//! reader: &mut R,
66//! code: SR,
67//! ) -> Result<u64, R::Error> {
68//! Ok(code.read(reader)? + code.read(reader)?)
69//! }
70//! ```
71//!
72//! Note that the syntax for invoking the methods in the two groups of traits is
73//! identical, but the type variables are on the method in the first case, and
74//! on the trait in the second case.
75//!
76//! # Implementations
77//!
78//! The [`Codes`] enum variants represent all the available codes. [`Codes`]
79//! implements all the dispatch traits, so it can be used to read or write any
80//! code both in a generic and in a specific way. It also implements the
81//! [`CodeLen`] trait, which provides a method to compute the length of a
82//! codeword. The only exception is for [minimal binary codes], which have a
83//! separate [`MinimalBinary`] structure with the same functionality, as they
84//! cannot represent all integers.
85//!
86//! If Rust supported const enums in traits, one could create structures
87//! with const enum type parameters of type [`Codes`], and then the compiler
88//! would be able to optimize away the code selection at compile time. However,
89//! this is not currently possible, so we provide a workaround using a
90//! zero-sized struct with a `const usize` parameter, [`ConstCode`], that
91//! implements all the dispatch traits and [`CodeLen`], and can be used to
92//! select the code at compile time. The parameter must be taken from the
93//! [`code_consts`] module, which contains constants for all parameterless
94//! codes, and for the codes with parameters up to 10. For example, here at
95//! execution time there will be no test to select a code, even if
96//! `read_two_codes_and_sum` is generic:
97//! ```rust
98//! use dsi_bitstream::prelude::*;
99//! use dsi_bitstream::dispatch::{code_consts, CodesRead, DynamicCodeRead};
100//! use std::fmt::Debug;
101//!
102//! fn read_two_codes_and_sum<
103//! E: Endianness,
104//! R: CodesRead<E> + ?Sized,
105//! GR: DynamicCodeRead
106//! >(
107//! reader: &mut R,
108//! code: GR,
109//! ) -> Result<u64, R::Error> {
110//! Ok(code.read(reader)? + code.read(reader)?)
111//! }
112//!
113//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
114//! reader: &mut R,
115//! ) -> Result<u64, R::Error> {
116//! read_two_codes_and_sum(reader, ConstCode::<{code_consts::GAMMA}>)
117//! }
118//! ```
119//!
120//! Working with [`ConstCode`] is very efficient, but it forces the choice of a
121//! code at compile time. If you need to read or write a code multiple times on
122//! the same type of bitstream, you can use the structs [`FuncCodeReader`] and
123//! [`FuncCodeWriter`], which implement [`StaticCodeRead`] and
124//! [`StaticCodeWrite`] by storing a function pointer.
125//!
126//! A value of type [`FuncCodeReader`] or [`FuncCodeWriter`] can be created by
127//! calling their `new` method with a variant of the [`Codes`] enum. As in the
128//! case of [`ConstCode`], there are pointers for all parameterless codes, and
129//! for the codes with parameters up to 10, and the method will return an error
130//! if the code is not supported.
131//!
132//! For example:
133//! ```rust
134//! use dsi_bitstream::prelude::*;
135//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
136//! use std::fmt::Debug;
137//!
138//! fn read_two_codes_and_sum<
139//! E: Endianness,
140//! R: CodesRead<E> + ?Sized,
141//! SR: StaticCodeRead<E, R>
142//! >(
143//! reader: &mut R,
144//! code: SR,
145//! ) -> Result<u64, R::Error> {
146//! Ok(code.read(reader)? + code.read(reader)?)
147//! }
148//!
149//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
150//! reader: &mut R,
151//! ) -> Result<u64, R::Error> {
152//! read_two_codes_and_sum(reader, FuncCodeReader::new(Codes::Gamma).unwrap())
153//! }
154//! ```
155//! Note that we [`unwrap`] the result of the [`new`] method, as we know that a
156//! function pointer exists for the γ code.
157//!
158//! # Workaround to Limitations
159//!
160//! Both [`ConstCode`] and [`FuncCodeReader`] / [`FuncCodeWriter`] are limited
161//! to a fixed set of codes. If you need to work with a code that is not
162//! supported by them, you can implement your own version. For example, here we
163//! define a zero-sized struct that represents a Rice code with a fixed parameter
164//! `LOG2_B`:
165//! ```rust
166//! use dsi_bitstream::prelude::*;
167//! use dsi_bitstream::dispatch::{CodesRead, CodesWrite};
168//! use dsi_bitstream::dispatch::{DynamicCodeRead, DynamicCodeWrite};
169//! use std::fmt::Debug;
170//!
171//! #[derive(Clone, Copy, Debug, Default)]
172//! pub struct Rice<const LOG2_B: usize>;
173//!
174//! impl<const LOG2_B: usize> DynamicCodeRead for Rice<LOG2_B> {
175//! fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
176//! &self,
177//! reader: &mut CR,
178//! ) -> Result<u64, CR::Error> {
179//! reader.read_rice(LOG2_B)
180//! }
181//! }
182//!
183//! impl<const LOG2_B: usize> DynamicCodeWrite for Rice<LOG2_B> {
184//! fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
185//! &self,
186//! writer: &mut CW,
187//! n: u64,
188//! ) -> Result<usize, CW::Error> {
189//! writer.write_rice(n, LOG2_B)
190//! }
191//! }
192//!
193//! impl<const LOG2_B: usize> CodeLen for Rice<LOG2_B> {
194//! #[inline]
195//! fn len(&self, n: u64) -> usize {
196//! len_rice(n, LOG2_B)
197//! }
198//! }
199//! ```
200//!
201//! Suppose instead you need to pass a [`StaticCodeRead`] to a method using a
202//! code that is not supported directly by [`FuncCodeReader`]. You can create a
203//! new [`FuncCodeReader`] using a provided function:
204//! ```rust
205//! use dsi_bitstream::prelude::*;
206//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
207//! use std::fmt::Debug;
208//!
209//! fn read_two_codes_and_sum<
210//! E: Endianness,
211//! R: CodesRead<E> + ?Sized,
212//! SR: StaticCodeRead<E, R>
213//! >(
214//! reader: &mut R,
215//! code: SR,
216//! ) -> Result<u64, R::Error> {
217//! Ok(code.read(reader)? + code.read(reader)?)
218//! }
219//!
220//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
221//! reader: &mut R,
222//! ) -> Result<u64, R::Error> {
223//! read_two_codes_and_sum(reader, FuncCodeReader::new_with_func(|r: &mut R| r.read_rice(20)))
224//! }
225//! ```
226//!
227//! [codes]: super::codes
228//! [minimal binary codes]: crate::codes::minimal_binary
229//! [`unwrap`]: core::result::Result::unwrap
230//! [`new`]: FuncCodeReader::new
231
232use crate::prelude::Endianness;
233use crate::prelude::{BitRead, BitWrite};
234
235use crate::codes::*;
236
237pub mod codes;
238pub use codes::*;
239
240/// Error returned when a code is not supported for dispatch.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
242pub enum DispatchError {
243 /// The code is not supported for dynamic dispatch.
244 UnsupportedCode(Codes),
245 /// The code constant is not supported.
246 UnsupportedCodeConst(usize),
247}
248
249impl core::fmt::Display for DispatchError {
250 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
251 match self {
252 Self::UnsupportedCode(code) => {
253 write!(f, "unsupported dispatch for code {:?}", code)
254 }
255 Self::UnsupportedCodeConst(c) => {
256 write!(f, "unsupported code constant {}", c)
257 }
258 }
259 }
260}
261
262impl core::error::Error for DispatchError {}
263
264pub mod r#static;
265pub use r#static::{ConstCode, code_consts};
266
267pub mod dynamic;
268pub use dynamic::{FuncCodeLen, FuncCodeReader, FuncCodeWriter};
269
270pub mod factory;
271pub use factory::{CodesReaderFactory, CodesReaderFactoryHelper, FactoryFuncCodeReader};
272
273/// Convenience extension trait for reading all the codes supported by the
274/// library.
275///
276/// A blanket implementation is provided for all types that implement the
277/// necessary traits.
278///
279/// This trait is mainly useful internally to implement the dispatch
280/// traits [`DynamicCodeRead`], [`StaticCodeRead`], [`DynamicCodeWrite`], and
281/// [`StaticCodeWrite`]. The user might find it more useful to define their own
282/// convenience trait that includes only the codes they need.
283pub trait CodesRead<E: Endianness>:
284 BitRead<E>
285 + GammaRead<E>
286 + DeltaRead<E>
287 + ZetaRead<E>
288 + OmegaRead<E>
289 + PiRead<E>
290 + MinimalBinaryRead<E>
291 + GolombRead<E>
292 + RiceRead<E>
293 + ExpGolombRead<E>
294 + VByteBeRead<E>
295 + VByteLeRead<E>
296{
297}
298
299impl<
300 E: Endianness,
301 B: BitRead<E>
302 + GammaRead<E>
303 + DeltaRead<E>
304 + ZetaRead<E>
305 + OmegaRead<E>
306 + PiRead<E>
307 + MinimalBinaryRead<E>
308 + GolombRead<E>
309 + RiceRead<E>
310 + ExpGolombRead<E>
311 + VByteBeRead<E>
312 + VByteLeRead<E>,
313> CodesRead<E> for B
314{
315}
316
317/// Convenience extension trait for writing all the codes supported by the
318/// library.
319///
320/// A blanket implementation is provided for all types that implement the
321/// necessary traits.
322///
323/// This trait is mainly useful internally to implement the dispatch
324/// traits [`DynamicCodeWrite`] and [`StaticCodeWrite`]. The user might find it
325/// more useful to define their own convenience trait that includes only the
326/// codes they need.
327pub trait CodesWrite<E: Endianness>:
328 BitWrite<E>
329 + GammaWrite<E>
330 + DeltaWrite<E>
331 + ZetaWrite<E>
332 + OmegaWrite<E>
333 + MinimalBinaryWrite<E>
334 + PiWrite<E>
335 + GolombWrite<E>
336 + RiceWrite<E>
337 + ExpGolombWrite<E>
338 + VByteBeWrite<E>
339 + VByteLeWrite<E>
340{
341}
342
343impl<
344 E: Endianness,
345 B: BitWrite<E>
346 + GammaWrite<E>
347 + DeltaWrite<E>
348 + ZetaWrite<E>
349 + OmegaWrite<E>
350 + MinimalBinaryWrite<E>
351 + PiWrite<E>
352 + GolombWrite<E>
353 + RiceWrite<E>
354 + ExpGolombWrite<E>
355 + VByteBeWrite<E>
356 + VByteLeWrite<E>,
357> CodesWrite<E> for B
358{
359}
360
361/// A trait providing a method to read a code from a generic [`CodesRead`].
362///
363/// The difference with [`StaticCodeRead`] is that this trait is more generic,
364/// as the [`CodesRead`] is a parameter of the method, and not of the trait.
365pub trait DynamicCodeRead {
366 fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
367 &self,
368 reader: &mut CR,
369 ) -> Result<u64, CR::Error>;
370}
371
372/// A trait providing a method to write a code to a generic [`CodesWrite`].
373///
374/// The difference with [`StaticCodeWrite`] is that this trait is more generic,
375/// as the [`CodesWrite`] is a parameter of the method, and not of the trait.
376pub trait DynamicCodeWrite {
377 fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
378 &self,
379 writer: &mut CW,
380 n: u64,
381 ) -> Result<usize, CW::Error>;
382}
383
384/// A trait providing a method to read a code from a [`CodesRead`] specified as
385/// trait type parameter.
386///
387/// The difference with [`DynamicCodeRead`] is that this trait is
388/// more specialized, as the [`CodesRead`] is a parameter of the
389/// trait.
390///
391/// For a fixed code this trait may be implemented by storing
392/// a function pointer.
393pub trait StaticCodeRead<E: Endianness, CR: CodesRead<E> + ?Sized> {
394 fn read(&self, reader: &mut CR) -> Result<u64, CR::Error>;
395}
396
397/// A trait providing a method to write a code to a [`CodesWrite`]
398/// specified as a trait type parameter.
399///
400/// The difference with [`DynamicCodeWrite`] is that this trait is
401/// more specialized, as the [`CodesWrite`] is a parameter of the
402/// trait.
403///
404/// For a fixed code this trait may be implemented by storing a function
405/// pointer.
406pub trait StaticCodeWrite<E: Endianness, CW: CodesWrite<E> + ?Sized> {
407 fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error>;
408}
409
410/// A trait providing a generic method to compute the length of a codeword.
411pub trait CodeLen {
412 /// Returns the length of the codeword for `n`.
413 #[must_use]
414 fn len(&self, n: u64) -> usize;
415}