6

At the moment, implementing the std::ops::IndexMut trait on a type in Rust requires that I also implement the std::ops::Index trait as well. The bodies of these implementations end up being virtually identical. For example:

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() {}

This works, and obviously for trivial types this isn't a serious problem, but for more complex types with more interesting indexing this quickly becomes laborious and error-prone. I'm scratching my head trying to find a way to unify this code, but nothing is jumping out at me, and yet I feel there has to/should be a way to do this without essentially having to copy and paste. Any suggestions? What am I missing?

1

1 Answer 1

5

Unfortunately, this cuts across a few things Rust really isn't good at right now. The cleanest solution I could come up with was this:

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]);
}

The short version is: we turn the body of index/index_mut into a macro so that we can substitute the name of a different macro that, given an expression, expands to either &expr or &mut expr. We also have to re-capture the self parameter (using a different name) because self is really weird in Rust, and I gave up trying to make it work nicely.

Sign up to request clarification or add additional context in comments.

5 Comments

Do you feel that this code is actually better? I wonder what the amount of repetition would need to be before I was like "yeah, this makes it easier to understand" ^_^
@Shepmaster Well, that is the issue. If anyone comes up with a terser solution, I'd love to see it.
What are the chances of someone outdoing you, the Master of Macros? ;-)
@Shepmaster Probably around about the same time Duke Nukem Forever comes out. ... wait, hang on...
The macro does look a little goofy, but the more I think about this the more I can see that a macro of some kind really is the right solution here. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.