Skip to content

Make TryFrom and FromStr infallible if there's a default#476

Merged
Peternator7 merged 5 commits intomasterfrom
peternator7/infallible-from-str
Feb 22, 2026
Merged

Make TryFrom and FromStr infallible if there's a default#476
Peternator7 merged 5 commits intomasterfrom
peternator7/infallible-from-str

Conversation

@Peternator7
Copy link
Owner

@Peternator7 Peternator7 commented Feb 22, 2026

Fixes #255 and #307. the PR makes EnumString implement From<&str> instead of TryFrom if there is a #[strum(default)] attribute on one of the variants.

Incorporates ideas from #432, but decided the breaking change is probably acceptable since it's more accurate to
say the parse is infallible when it is.

*If you come across this and want the old behavior, you can do this:

#[derive(EnumString)]
#[strum(parse_error_ty = strum::ParseError, parse_error_fn = make_error)]
pub enum Color {
    Red,
    Blue
}

fn make_error(x: &str) -> strum::ParseError {
    strum::ParseError
}

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements infallible parsing for EnumString when a #[strum(default)] variant is present. The key change is that enums with a default variant now derive From<&str> instead of TryFrom<&str>, making parsing infallible (unless a custom error type is specified). This addresses issues #255 and #307, and incorporates ideas from PR #432.

Changes:

  • Modified EnumString to derive From<&str> instead of TryFrom<&str> when a default variant exists and no custom error type is set
  • Updated error type logic to use Infallible for default variants without custom error types
  • Added comprehensive tests for infallible parsing including PHF support and custom error type interactions

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
strum_macros/src/macros/strings/from_string.rs Core implementation: refactored to conditionally generate From<&str> vs TryFrom<&str> based on presence of default variant and custom error type
strum_macros/src/lib.rs Updated EnumString documentation to explain infallible parsing behavior and custom error type interactions
strum_tests/tests/from_str.rs Added tests for infallible parsing, custom error types with defaults, and the new From<&str> implementation
strum_tests/tests/phf.rs Added tests for PHF with infallible parsing, including case-insensitive variants
strum_tests/src/lib.rs Added #[strum(default)] to test enum to support infallible parsing tests

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +148 to +151
} else if has_custom_err_ty {
// The user defined a custom error type, but not a custom error function. This is an error
// if the method isn't infallible.
return Err(missing_parse_err_attr_error());
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message is misleading in this context. Here, the user has provided parse_err_ty but not parse_err_fn, and there's no default variant. The error message "parse_err_ty and parse_err_fn attributes are both required" suggests both are always required together, but after the infallible parsing changes, parse_err_fn is only required when there's no default variant. Consider a more specific error message like "parse_err_fn is required when parse_err_ty is specified without a default variant" or create a new error function for this specific case.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to 70
/// The default error type is `strum::ParseError`. This can be overridden by applying both the
/// `parse_err_ty` and `parse_err_fn` attributes at the type level. `parse_err_fn` should be a
/// function that accepts an `&str` and returns the type `parse_err_ty`. See [this test
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states that both parse_err_ty and parse_err_fn attributes must be provided to override the error type. However, with the infallible parsing changes, if there's a #[strum(default)] variant, only parse_err_ty is required (see from_string.rs:148-151). Consider updating this to clarify that parse_err_fn is only required when there's no default variant, e.g., "This can be overridden by applying the parse_err_ty attribute (and parse_err_fn when there's no default variant)."

Suggested change
/// The default error type is `strum::ParseError`. This can be overridden by applying both the
/// `parse_err_ty` and `parse_err_fn` attributes at the type level. `parse_err_fn` should be a
/// function that accepts an `&str` and returns the type `parse_err_ty`. See [this test
/// The default error type is `strum::ParseError`. This can be overridden by applying the
/// `parse_err_ty` attribute (and `parse_err_fn` when there's no `#[strum(default)]` variant) at
/// the type level. `parse_err_fn` should be a function that accepts an `&str` and returns the
/// type `parse_err_ty`. See [this test

Copilot uses AI. Check for mistakes.
@Peternator7 Peternator7 merged commit 9334c72 into master Feb 22, 2026
10 checks passed
@nazar-pc
Copy link

Very questionable decision IMHO. It is very confusing to a developer when invalid string results in successful conversion. If anyone wants that, they'd just do .unwrap_or_default() on the result instead in those rare special cases.

Most of the time though developers want to know whether the input was correct or not instead of silently getting the default value.

@Peternator7
Copy link
Owner Author

Maybe the title of the PR isn't entirely clear. This PR clarified existing behavior more than changing anything in a meaningful way. By default, EnumString still generates FromStr<T, ParseError> so you can handle the error however you'd like. However, if you included #[strum(default)] on a variant, that would become the "default" output if none of the other options matched, and it would capture the input string. This was always infallible, but strum continued to generate TryFrom even though the conversion couldn't fail. This PR just changed the behavior in the infallible case to more accurately reflect that the conversion can't fail if you have applied #[strum(default)] to one of the variants. Otherwise, the behavior remains the same.

@nazar-pc
Copy link

I think I didn't read it carefully enough and somehow assumed it was #[default] rather than #[strum(default)]. It is not as bad as I thought it was initially. Thanks for explaining.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

strum::EnumString deriving TryFrom<&str> means From<&str> cannot be implemented

3 participants