Наследование трейтов в Rust для подмножества реализаций трейтов

Вопрос или проблема

Наследование трейтов в Rust для подмножества реализаций трейтов

Я не совсем уверен, как именно сформулировать этот вопрос, поэтому проиллюстрирую его на примере.

Я использую библиотеку bitflags, которая предоставляет трейты под названием Flags. Трейт флагов включает наличие связанного типа, нечто вроде

pub trait Flags: Sized + 'static {
    /// Тип базовых битов.
    type Bits: Bits;

    fn from_bits(bits: Self::Bits) -> Option<Self> {
        ... 
    }

    ...
}

где трейт Bits реализован для следующих типов

i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize

Затем я хочу определить трейт (Register), который наследует Flags, но добавляет требование о том, что связ常ная константа должна быть определена при реализации. Не что-то вроде

trait Register: Flags {
    const ADDRESS: u8;

    fn address(&self) -> u8 {
        Self::ADDRESS
    }
}

Имение доступа к Flags с прикрепленным адресом позволило бы мне делать полезные вещи, но некоторые из этих полезных вещей зависят от реализации Flags, чтобы тип Bits был u8. Я оказываюсь в ситуации, когда у меня есть

trait Register: Flags {
    const ADDRESS: u8;

    fn address(&self) -> u8 {
        Self::ADDRESS
    }

    fn do_something(&self) {
        let new_bits_data: u8 = ...;
        *self = Self::from_bits(new_bits_data).unwrap()
    }
}

И вызов Self::from_bits() не работает, потому что мы еще не знаем тип Bits, который будет у конкретной реализации Flags.

В моем сознании я хотел бы иметь возможность определить трейт Register только для подмножества реализаций Flags, которые имеют установленный

type Bits = u8;

но я не знаю, является ли это хорошей идеей или даже возможным.

У вас есть идеи, как я мог бы обойти эту проблему или возможно ли реализовать то, что я хочу?

Я рассматривал идею определения трейта Flags_u8, который такой же, как Flags, с ограничением, что тип Bits — это u8. Но я не знаю, как это будет выглядеть.

Вы можете ограничить связанные типы супер-трейтов аналогично тому, как это можно сделать для любого трейта:

trait Register: Flags<Bits = u8> { ... }

Или для конкретного метода:

trait Register: Flags {
    const ADDRESS: u8;

    fn address(&self) -> u8 {
        Self::ADDRESS
    }

    fn do_something(&self)
    where
        Self: Flags<Bits = u8>,
    {
        let new_bits_data: u8 = ...;
        *self = Self::from_bits(new_bits_data).unwrap()
    }
}

Ответ или решение

Ваш вопрос о наследовании трейтов в Rust касается обобщений и ограничения ассоциированных типов, что действительно является интересной и немного сложной темой. Давайте рассмотрим, как вы можете решать проблему с наследованием трейта Flags, чтобы учитывать только те реализации, которые имеют ассоциированный тип Bits, равный u8.

Общее Решение

Вы можете ограничить ассоциированные типы супертрейтов аналогично тому, как это делается для любых других трейтов. Вот пример, как вы можете определить трейт Register с ограничением, что Bits должно быть u8.

Решение 1: Ограничение ассоциированного типа в определении трейта

trait Register: Flags<Bits = u8> {
    const ADDRESS: u8;

    fn address(&self) -> u8 {
        Self::ADDRESS
    }

    fn do_something(&mut self) {
        let new_bits_data: u8 = ...; // Ваши данные для битов
        *self = Self::from_bits(new_bits_data).unwrap();
    }
}

В этом примере мы явно указываем, что реализация трейта Register должна иметь Bits равным u8. Это позволяет вам использовать метод from_bits с типом u8.

Решение 2: Ограничение метода внутри трейта

Если вы хотите оставить возможность реализации Register для всегда как Flags, вы можете установить ограничение непосредственно внутри метода:

trait Register: Flags {
    const ADDRESS: u8;

    fn address(&self) -> u8 {
        Self::ADDRESS
    }

    fn do_something(&mut self)
    where
        Self: Flags<Bits = u8>, // Ограничение для метода
    {
        let new_bits_data: u8 = ...; // Ваши данные для битов
        *self = Self::from_bits(new_bits_data).unwrap();
    }
}

В этом варианте трейта Register, вы делаете так, что метод do_something может быть вызван только для тех реализаций Flags, которые имеют Bits равный u8. Это полезно, если вы хотите, чтобы другие методы трейта Register, которые не зависят от типа Bits, могли использоваться для других типов.

Заключение

Таким образом, вы можете настроить ваши трейты так, чтобы они работали только с нужными вам реализациями. Обратите внимание, что при использовании этих ограничений компилятор будет гарантировать, что ваш код останется безопасным, и при этом вы сможете воспользоваться всей мощью Rust в работе с абстракциями и обобщениями.

Если у вас есть дальнейшие вопросы или вам нужно разъяснение по определенным моментам, пожалуйста, дайте знать!

Оцените материал
Добавить комментарий

Капча загружается...