目前,在Rust中的类型上实现std::ops::IndexMut
特征要求我也实现std::ops::Index
特征。这些实现的主体最终几乎完全相同。例如:
use std::ops::{Index, IndexMut};
enum IndexType {
A,
B,
}
struct Indexable {
a: u8,
b: u8,
}
impl Index<IndexType> for Indexable {
type Output = u8;
fn index<'a>(&'a self, idx: IndexType) -> &'a u8 {
match idx {
IndexType::A => &self.a,
IndexType::B => &self.b,
}
}
}
impl IndexMut<IndexType> for Indexable {
fn index_mut<'a>(&'a mut self, idx: IndexType) -> &'a mut u8 {
match idx {
IndexType::A => &mut self.a,
IndexType::B => &mut self.b,
}
}
}
fn main() {}
这很有效,显然对于琐碎的类型来说,这不是一个严重的问题,但对于更复杂的类型,索引更有趣,这很快变得费力且容易出错。我试图找到一种方法来统一这个代码,但是没有任何东西在向我跳出来,但我觉得必须/应该是一种方法来做到这一点,而不必复制和粘贴。有什么建议?我错过了什么?
答案 0 :(得分:5)
不幸的是,这一切都扼杀了Rust现在并不擅长的一些事情。我能想到的最干净的解决方案是:
macro_rules! as_expr {
($e:expr) => { $e };
}
macro_rules! borrow_imm { ($e:expr) => { &$e } }
macro_rules! borrow_mut { ($e:expr) => { &mut $e } }
macro_rules! impl_index {
(
<$idx_ty:ty> for $ty:ty,
($idx:ident) -> $out_ty:ty,
$($body:tt)*
) => {
impl ::std::ops::Index<$idx_ty> for $ty {
type Output = $out_ty;
fn index(&self, $idx: $idx_ty) -> &$out_ty {
macro_rules! index_expr { $($body)* }
index_expr!(self, borrow_imm)
}
}
impl ::std::ops::IndexMut<$idx_ty> for $ty {
fn index_mut(&mut self, $idx: $idx_ty) -> &mut $out_ty {
macro_rules! index_expr { $($body)* }
index_expr!(self, borrow_mut)
}
}
};
}
enum IndexType { A, B }
struct Indexable { a: u8, b: u8 }
impl_index! {
<IndexType> for Indexable,
(idx) -> u8,
($this:expr, $borrow:ident) => {
match idx {
IndexType::A => $borrow!($this.a),
IndexType::B => $borrow!($this.b),
}
}
}
fn main() {
let mut x = Indexable { a: 1, b: 2 };
x[IndexType::A] = 3;
println!("x {{ a: {}, b: {} }}", x[IndexType::A], x[IndexType::B]);
}
简短版本是:我们将index
/ index_mut
的正文转换为宏,以便我们可以替换给定表达式的不同宏的名称,扩展为 &expr
或&mut expr
。我们还必须重新捕获self
参数(使用不同的名称),因为Rust中的self
非常奇怪,我放弃了试图让它很好地工作。