Вопрос или проблема
Наследование трейтов в 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 в работе с абстракциями и обобщениями.
Если у вас есть дальнейшие вопросы или вам нужно разъяснение по определенным моментам, пожалуйста, дайте знать!