在Rust中实现Index和IndexMut时如何避免冗余代码

时间:2016-06-20 03:48:05

标签: rust

目前,在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() {}

这很有效,显然对于琐碎的类型来说,这不是一个严重的问题,但对于更复杂的类型,索引更有趣,这很快变得费力且容易出错。我试图找到一种方法来统一这个代码,但是没有任何东西在向我跳出来,但我觉得必须/应该是一种方法来做到这一点,而不必复制和粘贴。有什么建议?我错过了什么?

1 个答案:

答案 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 非常奇怪,我放弃了试图让它很好地工作。