Skip to main content

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 LGPL-2.1-or-later
7 */
8
9//! Programmable static and dynamic dispatch for codes.
10//!
11//! The modules in [codes](super::codes), such as [`omega`], extend [`BitRead`]
12//! and [`BitWrite`] to provide a way to read and write codes from a bitstream.
13//! The 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
83//! codes](crate::codes::minimal_binary), which have a separate
84//! [`MinimalBinary`] structure with the same functionality, as they cannot
85//! represent all integers.
86//!
87//! If Rust supported const enums in traits, one could create structures
88//! with const enum type parameters of type [`Codes`], and then the compiler
89//! would be able to optimize away the code selection at compile time. However,
90//! this is not currently possible, so we provide a workaround using a
91//! zero-sized struct with a `const usize` parameter, [`ConstCode`], that
92//! implements all the dispatch traits and [`CodeLen`], and can be used to
93//! select the code at compile time. The parameter must be taken from the
94//! [`code_consts`] module, which contains constants for all parameterless
95//! codes, and for the codes with parameters up to 10. For example, here at
96//! execution time there will be no test to select a code, even if
97//! `read_two_codes_and_sum` is generic:
98//!```rust
99//! use dsi_bitstream::prelude::*;
100//! use dsi_bitstream::dispatch::{code_consts, CodesRead, DynamicCodeRead};
101//! use std::fmt::Debug;
102//!
103//! fn read_two_codes_and_sum<
104//!     E: Endianness,
105//!     R: CodesRead<E> + ?Sized,
106//!     GR: DynamicCodeRead
107//! >(
108//!     reader: &mut R,
109//!     code: GR,
110//! ) -> Result<u64, R::Error> {
111//!     Ok(code.read(reader)? + code.read(reader)?)
112//! }
113//!
114//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
115//!     reader: &mut R,
116//! ) -> Result<u64, R::Error> {
117//!     read_two_codes_and_sum(reader, ConstCode::<{code_consts::GAMMA}>)
118//! }
119//!```
120//!
121//! Working with [`ConstCode`] is very efficient, but it forces the choice of a
122//! code at compile time. If you need to read or write a code multiple times on
123//! the same type of bitstream, you can use the structs [`FuncCodeReader`] and
124//! [`FuncCodeWriter`], which implement [`StaticCodeRead`] and
125//! [`StaticCodeWrite`] by storing a function pointer.
126//!
127//! A value of type [`FuncCodeReader`] or [`FuncCodeWriter`] can be created by
128//! calling their `new` method with a variant of the [`Codes`] enum. As in the
129//! case of [`ConstCode`], there are pointers for all parameterless codes, and
130//! for the codes with parameters up to 10, and the method will return an error
131//! if the code is not supported.
132//!
133//! For example:
134//!```rust
135//! use dsi_bitstream::prelude::*;
136//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
137//! use std::fmt::Debug;
138//!
139//! fn read_two_codes_and_sum<
140//!     E: Endianness,
141//!     R: CodesRead<E> + ?Sized,
142//!     SR: StaticCodeRead<E, R>
143//! >(
144//!     reader: &mut R,
145//!     code: SR,
146//! ) -> Result<u64, R::Error> {
147//!     Ok(code.read(reader)? + code.read(reader)?)
148//! }
149//!
150//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
151//!     reader: &mut R,
152//! ) -> Result<u64, R::Error> {
153//!     read_two_codes_and_sum(reader, FuncCodeReader::new(Codes::Gamma).unwrap())
154//! }
155//!```
156//! Note that we [`unwrap`](core::result::Result::unwrap) the result of the
157//! [`new`](FuncCodeReader::new) method, as we know that a function pointer
158//! exists for the γ code.
159//!
160//! # Workaround to Limitations
161//!
162//! Both [`ConstCode`] and [`FuncCodeReader`] / [`FuncCodeWriter`] are limited
163//! to a fixed set of codes. If you need to work with a code that is not
164//! supported by them, you can implement your own version. For example, here we
165//! define a zero-sized struct that represents a Rice code with a fixed parameter
166//! `LOG2_B`:
167//! ```rust
168//! use dsi_bitstream::prelude::*;
169//! use dsi_bitstream::dispatch::{CodesRead, CodesWrite};
170//! use dsi_bitstream::dispatch::{DynamicCodeRead, DynamicCodeWrite};
171//! use std::fmt::Debug;
172//!
173//! #[derive(Clone, Copy, Debug, Default)]
174//! pub struct Rice<const LOG2_B: usize>;
175//!
176//! impl<const LOG2_B: usize> DynamicCodeRead for Rice<LOG2_B> {
177//!     fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
178//!         &self,
179//!         reader: &mut CR,
180//!     ) -> Result<u64, CR::Error> {
181//!         reader.read_rice(LOG2_B)
182//!     }
183//! }
184//!
185//! impl<const LOG2_B: usize> DynamicCodeWrite for Rice<LOG2_B> {
186//!     fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
187//!         &self,
188//!         writer: &mut CW,
189//!         n: u64,
190//!     ) -> Result<usize, CW::Error> {
191//!         writer.write_rice(n, LOG2_B)
192//!     }
193//! }
194//!
195//! impl<const LOG2_B: usize> CodeLen for Rice<LOG2_B> {
196//!     #[inline]
197//!     fn len(&self, n: u64) -> usize {
198//!         len_rice(n, LOG2_B)
199//!     }
200//! }
201//! ```
202//!
203//! Suppose instead you need to pass a [`StaticCodeRead`] to a method using a
204//! code that is not supported directly by [`FuncCodeReader`]. You can create a
205//! new [`FuncCodeReader`] using a provided function:
206//!```rust
207//! use dsi_bitstream::prelude::*;
208//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
209//! use std::fmt::Debug;
210//!
211//! fn read_two_codes_and_sum<
212//!     E: Endianness,
213//!     R: CodesRead<E> + ?Sized,
214//!     SR: StaticCodeRead<E, R>
215//! >(
216//!     reader: &mut R,
217//!     code: SR,
218//! ) -> Result<u64, R::Error> {
219//!     Ok(code.read(reader)? + code.read(reader)?)
220//! }
221//!
222//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
223//!     reader: &mut R,
224//! ) -> Result<u64, R::Error> {
225//!     read_two_codes_and_sum(reader, FuncCodeReader::new_with_func(|r: &mut R| r.read_rice(20)))
226//! }
227//!```
228
229use crate::prelude::Endianness;
230use crate::prelude::{BitRead, BitWrite};
231
232use crate::codes::*;
233
234pub mod codes;
235pub use codes::*;
236
237/// Error returned when a code is not supported for dispatch.
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239pub enum DispatchError {
240    /// The code is not supported for dynamic dispatch.
241    UnsupportedCode(Codes),
242    /// The code constant is not supported.
243    UnsupportedCodeConst(usize),
244}
245
246impl core::fmt::Display for DispatchError {
247    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
248        match self {
249            Self::UnsupportedCode(code) => {
250                write!(f, "unsupported dispatch for code {:?}", code)
251            }
252            Self::UnsupportedCodeConst(c) => {
253                write!(f, "unsupported code constant {}", c)
254            }
255        }
256    }
257}
258
259impl core::error::Error for DispatchError {}
260
261pub mod r#static;
262pub use r#static::{ConstCode, code_consts};
263
264pub mod dynamic;
265pub use dynamic::{FuncCodeLen, FuncCodeReader, FuncCodeWriter};
266
267pub mod factory;
268pub use factory::{CodesReaderFactory, CodesReaderFactoryHelper, FactoryFuncCodeReader};
269
270/// Convenience extension trait for reading all the codes supported by the
271/// library.
272///
273/// A blanket implementation is provided for all types that implement the
274/// necessary traits.
275///
276/// This trait is mainly useful internally to implement the dispatch
277/// traits [`DynamicCodeRead`], [`StaticCodeRead`], [`DynamicCodeWrite`], and
278/// [`StaticCodeWrite`]. The user might find it more useful to define their own
279/// convenience trait that includes only the codes they need.
280pub trait CodesRead<E: Endianness>:
281    BitRead<E>
282    + GammaRead<E>
283    + DeltaRead<E>
284    + ZetaRead<E>
285    + OmegaRead<E>
286    + PiRead<E>
287    + MinimalBinaryRead<E>
288    + GolombRead<E>
289    + RiceRead<E>
290    + ExpGolombRead<E>
291    + VByteBeRead<E>
292    + VByteLeRead<E>
293{
294}
295
296impl<E: Endianness, B> CodesRead<E> for B where
297    B: BitRead<E>
298        + GammaRead<E>
299        + DeltaRead<E>
300        + ZetaRead<E>
301        + OmegaRead<E>
302        + PiRead<E>
303        + MinimalBinaryRead<E>
304        + GolombRead<E>
305        + RiceRead<E>
306        + ExpGolombRead<E>
307        + VByteBeRead<E>
308        + VByteLeRead<E>
309{
310}
311
312/// Convenience extension trait for writing all the codes supported by the
313/// library.
314///
315/// A blanket implementation is provided for all types that implement the
316/// necessary traits.
317///
318/// This trait is mainly useful internally to implement the dispatch
319/// traits [`DynamicCodeWrite`] and [`StaticCodeWrite`]. The user might find it
320/// more useful to define their own convenience trait that includes only the
321/// codes they need.
322pub trait CodesWrite<E: Endianness>:
323    BitWrite<E>
324    + GammaWrite<E>
325    + DeltaWrite<E>
326    + ZetaWrite<E>
327    + OmegaWrite<E>
328    + MinimalBinaryWrite<E>
329    + PiWrite<E>
330    + GolombWrite<E>
331    + RiceWrite<E>
332    + ExpGolombWrite<E>
333    + VByteBeWrite<E>
334    + VByteLeWrite<E>
335{
336}
337
338impl<E: Endianness, B> CodesWrite<E> for B where
339    B: BitWrite<E>
340        + GammaWrite<E>
341        + DeltaWrite<E>
342        + ZetaWrite<E>
343        + OmegaWrite<E>
344        + MinimalBinaryWrite<E>
345        + PiWrite<E>
346        + GolombWrite<E>
347        + RiceWrite<E>
348        + ExpGolombWrite<E>
349        + VByteBeWrite<E>
350        + VByteLeWrite<E>
351{
352}
353
354/// A trait providing a method to read a code from a generic [`CodesRead`].
355///
356/// The difference with [`StaticCodeRead`] is that this trait is more generic,
357/// as the [`CodesRead`] is a parameter of the method, and not of the trait.
358pub trait DynamicCodeRead {
359    fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
360        &self,
361        reader: &mut CR,
362    ) -> Result<u64, CR::Error>;
363}
364
365/// A trait providing a method to write a code to a generic [`CodesWrite`].
366///
367/// The difference with [`StaticCodeWrite`] is that this trait is more generic,
368/// as the [`CodesWrite`] is a parameter of the method, and not of the trait.
369pub trait DynamicCodeWrite {
370    fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
371        &self,
372        writer: &mut CW,
373        n: u64,
374    ) -> Result<usize, CW::Error>;
375}
376
377/// A trait providing a method to read a code from a [`CodesRead`] specified as
378/// trait type parameter.
379///
380/// The difference with [`DynamicCodeRead`] is that this trait is
381/// more specialized, as the [`CodesRead`] is a parameter of the
382/// trait.
383///
384/// For a fixed code this trait may be implemented by storing
385/// a function pointer.
386pub trait StaticCodeRead<E: Endianness, CR: CodesRead<E> + ?Sized> {
387    fn read(&self, reader: &mut CR) -> Result<u64, CR::Error>;
388}
389
390/// A trait providing a method to write a code to a [`CodesWrite`]
391/// specified as a trait type parameter.
392///
393/// The difference with [`DynamicCodeWrite`] is that this trait is
394/// more specialized, as the [`CodesWrite`] is a parameter of the
395/// trait.
396///
397/// For a fixed code this trait may be implemented by storing a function
398/// pointer.
399pub trait StaticCodeWrite<E: Endianness, CW: CodesWrite<E> + ?Sized> {
400    fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error>;
401}
402
403/// A trait providing a generic method to compute the length of a codeword.
404pub trait CodeLen {
405    /// Returns the length of the codeword for `n`.
406    #[must_use]
407    fn len(&self, n: u64) -> usize;
408}