0

I'm trying to swap contents of a struct based on values passed in associated function. However, while using match, arms do not seem to recognize the value parameter, and thus arms after first one become unreachable.

#[derive(Debug, Copy, Clone)]
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
    pub a: u8,
}

impl Color {
    pub fn swap(mut self, first: u8, second: u8) -> Color {
        let mut swapped = self;

        match self {
            Color { r: first, g, b, a } => swapped.r = second,
            Color { r, g: first, b, a } => swapped.g = second,
            Color { r, g, b: first, a } => swapped.b = second,
            Color { r, b, g, a: first } => swapped.a = second,
        }

        match self {
            Color { r: second, g, b, a } => swapped.r = first,
            Color { r, g: second, b, a } => swapped.g = first,
            Color { r, g, b: second, a } => swapped.b = first,
            Color { r, g, b, a: second } => swapped.a = first,
        }

        self = swapped;
        self

    }
}

However, if I put an actual u8 like Color { r: 10, g,b,a }, then it works.

What am I doing wrong here?

volkan
  • 5
  • 2

1 Answers1

2

You're creating a new first binding, destructuring the Color. To perform variable value comparisons, you need a "match guard" like so:

match self {
    Color { r, g, b, a } if r == first => swapped.r = second,
    Color { r, g, b, a } if g == first => swapped.g = second,
    Color { r, g, b, a } if b == first => swapped.b = second,
    Color { r, b, g, a } if a == first => swapped.a = second,
    _ => { /* no match */ },
}

The final "match-all" arm is required because the patterns are non-exhaustive, so you need to tell Rust what to do in the case that none of the other arms match. For example, it may be appropriate to panic here to signal bad input, or modify your function to return Result<Color, SomeErrorType> and return Err from the match-all arm.

Note that since you only use one field in each arm, you can ignore the others:

match self {
    Color { r, .. } if r == first => swapped.r = second,
    Color { g, .. } if g == first => swapped.g = second,
    Color { b, .. } if b == first => swapped.b = second,
    Color { a, .. } if a == first => swapped.a = second,
    _ => { /* no match */ },
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • To perhaps clarify the logic behind this — Rust's matches have two "modes": when comparing against a literal or a `const` identifier, they do equality checking; any other identifier gets bound to the value at runtime. So `Color { r: 10, .. } => {...}` and `Color { r: my_const, .. } => {...}` would only match if the runtime value of `r` matches `10` or `my_const`, whereas `Color { r: my_let, .. } => {...}` always succeeds and binds `my_let` to the runtime value of `r`. – BallpointBen Sep 03 '22 at 17:53