Реализация статической проверки границ в Rust с использованием typenum

Постановка задачи

У меня есть библиотека синтаксического анализа Rust для буферов байтов с проводной кодировкой, и я пытаюсь создать структуру данных «представления» ByteSliceReader для написания субпарсеров, которые обеспечивают проверку границ во время компиляции. Поскольку дженерики const, по-видимому, не обладают достаточной функциональностью для этого (поправьте меня, если я ошибаюсь), я попробовал использовать крейт typenum. Однако я получаю странные/трудные для понимания ошибки компилятора, поэтому я ищу рекомендации по их устранению.

(Я не думаю, что смогу использовать generic-array, поскольку субпарсерам необходимо использовать такие функции, как f32::from_le_bytes, которые принимают [u8; 4].)

Выполнение

Структура данных выглядит примерно так (за исключением того, что на самом деле данные хранятся в кольцевом буфере, а не в массиве):

use typenum::*;

struct ByteSliceReader<'a, const LEN: usize, Cursor> {
    buffer: &'a [u8; LEN],
    cursor: core::marker::PhantomData<Cursor>,
}

Здесь Cursor представляет собой беззнаковое целое число на основе typenum, которое отслеживает, сколько байтов было использовано субпарсером. У него есть один метод для «выталкивания» количества байтов, определенного константным обобщением:

impl<'a, const LEN: usize, Cursor: Unsigned> ByteSliceReader<'a, LEN, Cursor>
where
    Const<LEN>: ToUInt,
    U<LEN>: Unsigned,
{
    #[must_use]
    fn pop<const NUM: usize>(self) -> (ByteSliceReader<'a, LEN, Sum<Cursor, U<NUM>>>, [u8; NUM])
    where
        Const<NUM>: ToUInt,
        Cursor: core::ops::Add<U<NUM>>,
        Sum<Cursor, U<NUM>>: IsLessOrEqual<U<LEN>>,
    {
        let mut byte_window = [0; NUM];
        for (i, byte) in byte_window.iter_mut().enumerate() {
            *byte = self.buffer[Cursor::to_usize() + i];
        }

        (
            ByteSliceReader::<LEN, _> {
                buffer: self.buffer,
                cursor: core::marker::PhantomData,
            },
            byte_window,
        )
    }
}

Все это компилируется нормально. Но когда я пытаюсь использовать его в этом примере вспомогательной функции (предназначенной для извлечения и анализа некоторых байтов), она не компилируется:

fn get_serial_and_part_num<const LEN: usize, Cursor>(
    reader: ByteSliceReader<'_, LEN, Cursor>,
) -> (ByteSliceReader<'_, LEN, Sum<Sum<Cursor, U1>, U1>>, (u8, u8))
where
    Cursor: Unsigned,
    Cursor: core::ops::Add<U1>,
    Sum<Cursor, U1>: core::ops::Add<U1> + Unsigned,
    Const<LEN>: ToUInt,
    U<LEN>: Unsigned,
{
    let (reader, serial_num) = reader.pop::<1>();
    let (reader, part_num) = reader.pop::<1>();
    (reader, (serial_num[0], part_num[0]))
}

Ошибка

error[E0599]: the method `pop` exists for struct `ByteSliceReader<'_, LEN, Cursor>`, but its trait bounds were not satisfied
   --> fsw/sensors/ring_parser.rs:528:43
    |
483 |     struct ByteSliceReader<'a, const LEN: usize, Cursor> {
    |     ---------------------------------------------------- method `pop` not found for this struct
...
528 |         let (reader, serial_num) = reader.pop::<1>();
    |                                           ^^^ method cannot be called on `ByteSliceReader<'_, LEN, Cursor>` due to unsatisfied trait bounds
    |
    = note: the following trait bounds were not satisfied:
            `Cursor: Add<<typenum::Const<_> as typenum::ToUInt>::Output>`
help: consider restricting the type parameter to satisfy the trait bound
    |
526 |         U<LEN>: Unsigned, Cursor: Add<<typenum::Const<_> as typenum::ToUInt>::Output>
    |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Предложение компилятора содержит _, который я интерпретирую как 1, но его применение не помогает. Я пробовал различные перестановки границ и получал разные похожие ошибки. Есть ли способ добиться того, чего я хочу?


50
1

Ответ:

Решено

Непосредственная проблема заключается в том, что вам нужно сообщить компилятору, что Output из Const<1> as ToUInt будет U1:

    Const<1>: ToUInt<Output = U1>,

Однако это приводит к дальнейшим неудовлетворенным границам признаков (а именно к IsLessOrEqual):

    Sum<Cursor, U1>: IsLessOrEqual<U<LEN>>,
    Sum<Sum<Cursor, U1>, U1>: IsLessOrEqual<U<LEN>>,

Смотрите на детской площадке.