[Solved] Is adding an enum variant a breaking change?

#1

Assume I have an enum Error {A, B, C} that I use to return errors to the client.

Now I want to introduce another error variant D, so the former enum becomes enum Error {A, B, C, D}. Is this considered a breaking change that requires a major version increment? Because if some client exhaustively checks for all errors with a match, this change would break their code.
But on the other hand it feels a bit odd that everytime I include a new feature that comes with its own error type, I have to make a major version increment.

What do you think about this? How would you handle this example in your crates?

0 Likes

#2

Yes, because (you answered your own question):

#[non_exhaustive] is meant to solve this problem.

2 Likes

#3

Thank you very much! I have not heard of that rfc.

1 Like

#4

Until #[non_exhaustive] stabilizes, the approach I’ve seen people take is to add a hidden variant to the enum. This is a little bit clunky, and it’s a good idea to make a note of it in the enum’s docs, but it does the job for now.

1 Like

#5

I don’t really understand the use case in the initial question: surely, if one is adding a new feature, exerting that feature is going to go through new API functions that will be able to return new, different Error enums without affecting existing code?

Anyway, assuming that you do have to add a new error variant to an existing error enum, Clients can “opt-out” of a breaking change on an enum by always having a default arm branch:

#[allow(unreachable_patterns)]
match error {
    Error::A => { /* ... */ }
    Error::B => { /* ... */ }
    Error::C => { /* ... */ }
    _ => { /* special error-handling for unknown errors, may be unreachable if all variants are currently handled */ }
}

As a client of a library, I much prefer having to manually use this way of “opting-out” when the particular error variant doesn’t matter to me, rather than the library forcing that behavior on me by default.

Being able to exhaustively match on error variants of an enum is a great advantage of rust when you need precise control over what to do with all kinds of errors that can happen: if there is a newly added error variant to an error enum, then every place where you matched on this enum will automatically not compile anymore, so that you can precisely choose what to do of the new variants.
If I understand correctly, the #[non_exhaustive] attribute negates that advantage.

Yes, it means that adding a new error variant requires bumping the major version of the library, but I think this is to be expected: you are adding a new way for your library to fail, I don’t believe this behavior should be “silently” pushed downstream.

Now, that doesn’t mean that the #[non_exhaustive] attribute cannot have its uses, just that I do not think that it is fit for error handling in a library, in general…

2 Likes