Search code examples
enumsrustcomparison

How to avoid code duplication when comparing two enums' variants and their values?


I need to compare two serde_json::Values:

enum Value {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<Value>),
    Object(Map<String, Value>),
}

The comparison will return another enum:

enum Diff {
    Equal,
    Different,
    NotFound,
}

I started writing the code structured as follow:

fn compare(val1: &Value, val2: &Value) {
    let cmp = match val1 {
        Value::Null => {
            if let Value::Null = val2 {
                Diff::Equal
            } else {
                Diff::NotFound
            }
        }
        Value::Bool(b1) => {
            if let Value::Bool(b2) = val2 {
                if b1 == b2 {
                    Diff::Equal
                } else {
                    Diff::Different
                }
            } else {
                Diff::NotFound
            }
        }
        Value::Number(ref n1) => {
            if let Value::Number(ref n2) = val2 {
                if n1 == n2 {
                    Diff::Equal
                } else {
                    Diff::Different
                }
            } else {
                Diff::NotFound
            }
        }
        Value::String(ref s1) => {
            if let Value::String(ref s2) = val2 {
                if s1 == s2 {
                    Diff::Equal
                } else {
                    Diff::Different
                }
            } else {
                Diff::NotFound
            }
        }
        _ => {
            // etc...
            Diff::NotFound
        }
    };

}

There is a lot of code duplication that I'd like to avoid.

  1. Is there a better way to compare enums in general?
  2. How can I avoid this type of duplication (in C++ you could use macro for example)

Solution

  • You can match against 2 values simultaneously:

    let cmp = match (val1, val2) {
        (Value::Null, Value::Null) => Diff::Equal,
        _ => {
            // etc...
            Diff::NotFound
        }
    };
    

    You can also implement a conversion function or trait and use it:

    impl Diff {
        fn new<T: PartialEq>(lhs: T, rhs: T) -> Diff {
            if lhs == rhs { Diff::Equal } else { Diff::Different }
        }
    }
    
    fn compare(val1: &Value, val2: &Value) {
        let cmp = match (val1, val2) {
            (Value::Null, Value::Null) => Diff::Equal,
            (Value::Bool(b1), Value::Bool(b2)) => Diff::new(b1, b2),
            _ => Diff::NotFound,
        };
    }