2016-10-02 3 views
3

Допустим, у меня есть enum с несколькими вариантами:Аналогичные, но различные совпадения положения

#[derive(Copy,Clone)] 
enum Foo { 
    Left(isize), 
    Right(isize), 
    Other, 
    //... 
} 

И я хочу, чтобы шаблон матч на этом, с очень похожим, но не идентичным лечения Left и Right.

Я закончил с:

match foo { 
    Left(i) | Right(i) => { 
      let i2 = match foo { 
       Left(_) => -i, 
       Right(_) => i, 
       _ => unreachable!(), 
      }; 
      move_sideways(i2); 
      // non-trivial code, ie not a simple function call 
    }, 
    Other => { ... } 
} 

который не очень удовлетворительным, и опирается на не потребляя foo в первый раз. Есть ли хорошая идиома, которую я пропускаю, чтобы избежать повторного сопоставления или дублирования правила?

+4

Я не могу придумать лучшего способа сделать это. Однако взгляните на свой тип: 'Left (3)' и 'Right (-3)' будут кодировать точно такую ​​же информацию, не так ли? Таким образом, ваш тип несколько избыточен. Возможно, вы могли бы просто заменить 'Left' и' Right' на 'Sideways (isize)'. Конечно, он не решает, о чем вы просите, но * я думаю, что тип должен быть переработан ... Или я не прав? –

+1

Это просто недостаток моего упрощенного примера. Реальный тип - nom :: Input. –

ответ

2

Невозможно избежать дублирования правила без необходимости изменения структуры данных. Но если вы хотите, чтобы избежать Foo быть Copy типа (оставляя все остальное как есть), вот небольшая модификация:

let foo = Left(34); 
match foo { 
    foo2 @ Left(_) | foo2 @ Right(_) => { 
     let i = match foo2 { 
      Left(i) => -i, 
      Right(i) => i, 
      _ => unreachable!(), 
     }; 
     // ... non-trivial code 
    } 
    Other =>(), 
} 

Значение в foo будет двигаться к foo2.

(Возможно, это выглядит чище использовать затенение - foo вместо foo2 ...)

+0

Я не мог решить между ответами; выбрав этот, потому что я забыл про синтаксис @! –

2

Я бы, вероятно, пошел с предложением Лукаса, но я все равно пытался что-то придумать. Это может помочь вам, если вы не можете изменить способ построения Foo.

Я бы не сказал, что это супер-элегантный, но попытка была сделана:

use self::Foo::*; 

#[derive(Copy, Clone)] 
enum Foo { 
    Left(isize), 
    Right(isize), 
    Other, 
    //... 
} 

impl From<Foo> for Option<isize> { 
    fn from(foo: Foo) -> Option<isize> { 
     match foo { 
      Left(i) => Some(-i), 
      Right(i) => Some(i), 
      _ => None 
     } 
    } 
} 

fn main() { 
    let foo = Left(4); 

    if let Some(i) = <Option<isize>>::from(foo) { 
     move_sideways(i); 
    } else { 
     match foo { 
      Other => { }, 
      _ => unreachable!() 
     } 
    }; 
} 
1

Первая мысль, которая приходит на ум, чтобы обернуть ваш enum в Option вместо того, чтобы использовать Other конструктор:

enum Foo { 
    Left(isize), 
    Right(isize) 
} 

let foo = Some(Left(1)); 
let x = match foo { 
    Some(y) => { 
     let i2 = match y { 
      Left(i) => -i, 
      Right(i) => i 
     }; 
     do_something(i2) 
    }, 
    None => 0 
}; 

Если вы не хотите, чтобы сделать это, вы могли бы цепи два match -expressions и использовать первую обрабатывать первичную обработку Left и Right:

let bar = Right(2); 
let z = match match bar { 
    Left(i) => Some(-i), 
    Right(i) => Some(i), 
    Other => None 
} { 
    Some(i) => do_something(i), 
    None => 0 
}; 

Если это не помогает, потому что ваш случай является еще более сложным, я бы просто использовать один match -expression с отдельными случаями для Left, Right и Other и переместить общий код в функцию.