2016-06-20 6 views
4

Im Moment erfordert die Implementierung der std::ops::IndexMut Eigenschaft auf einen Typ in Rust, dass ich auch die std::ops::Index Eigenschaft implementieren. Die Körper dieser Implementierungen sind am Ende praktisch identisch. Zum Beispiel:So vermeiden Sie redundanten Code bei der Implementierung von Index und IndexMut in Rust

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

Dies funktioniert, und natürlich für trivial Typen ist dies kein ernstes Problem, aber für komplexere Typen mit interessanter Indizierung wird dies schnell mühsam und fehleranfällig. Ich kratze mich am Kopf und versuche einen Weg zu finden, diesen Code zu vereinheitlichen, aber nichts springt mir entgegen, und trotzdem muss ich ein Weg sein, dies zu tun, ohne im Wesentlichen kopieren und einfügen zu müssen. Irgendwelche Vorschläge? Was vermisse ich?

+0

Siehe diese Diskussion https://internals.rust-lang.org/t/parametrisierung-over-mutability/235, – malbarbo

Antwort

5

Leider ist dies ein paar Dinge, über die Rust im Moment nicht wirklich gut ist. Die sauberste Lösung, die ich kommen könnte mit war:

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

Die kurze Version ist: wir den Körper von index/index_mut in ein Makro drehen, so dass wir den Namen eines Makros verschiedenen ersetzen können, die angesichts ein Ausdruck, expandiert zu entweder&expr oder &mut expr. Wir auch müssen die self Parameter (mit einem anderen Namen) neu erfassen, weil ist wirklich seltsam in Rust, und ich gab es zu versuchen, damit es gut funktioniert.

+1

Fühlst du, dass dieser Code tatsächlich besser ist? Ich frage mich, wie viele Wiederholungen nötig sein würden, bevor ich "Ja, das macht es einfacher zu verstehen" ist.^_^ – Shepmaster

+0

@Shempmaster Nun, das * ist * das Problem. Wenn jemand eine Lösung von Terser hat, würde ich es gerne sehen. –

+0

Wie groß ist die Wahrscheinlichkeit, dass jemand dich, den Meister der Makros, übertrifft? ;-) – Shepmaster

Verwandte Themen