(inconsistencies or issues in bold)
-
std::fmt::Error:- is a (marker) struct
- does implement PartialEq (among other traits)
- does not implement a kind() method
- does not define ErrorKind variants
-
std::io::Error:- is a struct encapsulating an enum
- does not implement PartialEq
- does implement a kind() method
- does implements ErrorKind variants which implement PartialEq
-
std::error::Error- is a trait
- does not define or implement PartialEq
- does not define or implement a kind() method
- does hot define ErrorKind variants
error-chain- is Rust's defacto-standard error-handling crate
- does not implement PartialEq
- does implement a kind() method
- does implement ErrorKind variants which do not implement PartialEq
- pattern-matching (as opposed to via PartialEq) is often cited as the recommended way to distinguish Errors, given these numerous inconsistencies). There are crates (such as matches) for reducing boilerplate when pattern-matching
- pattern matching Errors adds boilerplate, obfuscates code and is an incomplete solution. e.g.:
fn string_validator_ref_handles_empty_input() {
// GivenNotes:
let input = String::new();
let expected_result = ErrorKind::ValueNone;
// When
let result = (&input).validate_ref::<NonEmptyStringValidator>().err().unwrap()
/* Idealized */
// Then
assert_eq!(*result, expected_result);
}
/* Reality */
// Then
assert!(matches!(*result, expected_result); //Error: pattern unreachable (match doesn't work this way; unintuitive at this level)
assert!(matches!(*result, val if val == expected_result)); //not ergonomic; Error: binary operation `==` cannot be applied to type `error::ErrorKind`
assert!(matches!(*result, ErrorKind::ValueNone); //works, but without variable support, important scenarios become unrepresentable:
// suppose two methods must yield same result. To ensure consistency through refactorings, a test is developed:
// assert!(matches(*result_a, *result_b) //not possible, as per above; constant solution not available