Loading pageā¦
Rust walkthroughs
Loading pageā¦
{"tool_call":"file.create","args":{"content":"# What are the differences between strum::EnumString and implementing std::str::FromStr manually for enums?
EnumString is a derive macro from the strum crate that automatically generates FromStr implementations for enums, handling parsing logic without boilerplate code. Manual FromStr implementation requires writing explicit pattern matching and error handling, but provides complete control over parsing semantics. The derive approach generates code at compile time that matches input strings against variant names or custom attributes, while manual implementation lets you define arbitrary parsing rules. Use EnumString for straightforward enum parsing where variants map directly to strings, and manual FromStr when you need custom parsing logic, validation, or string formats that differ from variant names.
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n#[derive(Debug, EnumString)]\nenum Color {\n Red,\n Green,\n Blue,\n}\n\nfn main() {\n // EnumString generates FromStr automatically\n let color = Color::from_str(\"Red\").unwrap();\n println!(\"{:?}\", color); // Red\n \n let color = Color::from_str(\"Green\").unwrap();\n println!(\"{:?}\", color); // Green\n \n // Case-sensitive by default\n let result = Color::from_str(\"red\");\n assert!(result.is_err());\n}\n\n\nEnumString generates FromStr that matches variant names exactly.
rust\nuse std::str::FromStr;\n\n#[derive(Debug, PartialEq)]\nenum Color {\n Red,\n Green,\n Blue,\n}\n\nimpl FromStr for Color {\n type Err = String;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n match s {\n \"Red\" => Ok(Color::Red),\n \"Green\" => Ok(Color::Green),\n \"Blue\" => Ok(Color::Blue),\n _ => Err(format!(\"unknown color: {}\", s)),\n }\n }\n}\n\nfn main() {\n let color = Color::from_str(\"Red\").unwrap();\n assert_eq!(color, Color::Red);\n \n let result = Color::from_str(\"invalid\");\n assert!(result.is_err());\n}\n\n\nManual implementation gives explicit control over each case.
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n#[derive(Debug, EnumString)]\nenum Status {\n // Custom string mapping with strum attribute\n #[strum(serialize = \"active\")]\n Active,\n \n #[strum(serialize = \"inactive\")]\n Inactive,\n \n // Multiple aliases\n #[strum(serialize = \"pending\", serialize = \"waiting\")]\n Pending,\n}\n\nfn main() {\n // Uses custom serialize strings\n let status = Status::from_str(\"active\").unwrap();\n println!(\"{:?}\", status); // Active\n \n let status = Status::from_str(\"inactive\").unwrap();\n println!(\"{:?}\", status); // Inactive\n \n // Both aliases work for Pending\n let status = Status::from_str(\"pending\").unwrap();\n assert_eq!(status, Status::Pending);\n \n let status = Status::from_str(\"waiting\").unwrap();\n assert_eq!(status, Status::Pending);\n}\n\n\nEnumString supports custom string mappings via attributes.
rust\nuse std::str::FromStr;\n\n#[derive(Debug, PartialEq)]\nenum Size {\n Small,\n Medium,\n Large,\n Custom(u32),\n}\n\nimpl FromStr for Size {\n type Err = String;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n match s.to_lowercase().as_str() {\n \"small\" | \"s\" => Ok(Size::Small),\n \"medium\" | \"m\" => Ok(Size::Medium),\n \"large\" | \"l\" => Ok(Size::Large),\n // Custom parsing logic: parse \"custom:123\"\n s if s.starts_with(\"custom:\") => {\n let num: u32 = s[7..].parse()\n .map_err(|_| \"invalid number\")?;\n Ok(Size::Custom(num))\n }\n _ => Err(format!(\"unknown size: {}\", s)),\n }\n }\n}\n\nfn main() {\n let size = Size::from_str(\"small\").unwrap();\n assert_eq!(size, Size::Small);\n \n let size = Size::from_str(\"S\").unwrap(); // Alias\n assert_eq!(size, Size::Small);\n \n let size = Size::from_str(\"custom:42\").unwrap(); // Custom value\n println!(\"{:?}\", size); // Custom(42)\n}\n\n\nManual FromStr handles complex parsing that derive macros cannot.
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n#[derive(Debug, EnumString)]\n#[strum(ascii_case_insensitive)]\nenum Direction {\n North,\n South,\n East,\n West,\n}\n\nfn main() {\n // Case-insensitive parsing\n let dir = Direction::from_str(\"north\").unwrap();\n assert_eq!(dir, Direction::North);\n \n let dir = Direction::from_str(\"NORTH\").unwrap();\n assert_eq!(dir, Direction::North);\n \n let dir = Direction::from_str(\"NoRtH\").unwrap();\n assert_eq!(dir, Direction::North);\n}\n\n\nEnumString supports case-insensitive parsing via attribute.
rust\nuse std::str::FromStr;\n\n#[derive(Debug, PartialEq)]\nenum Direction {\n North,\n South,\n East,\n West,\n}\n\nimpl FromStr for Direction {\n type Err = String;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n match s.to_lowercase().as_str() {\n \"north\" => Ok(Direction::North),\n \"south\" => Ok(Direction::South),\n \"east\" => Ok(Direction::East),\n \"west\" => Ok(Direction::West),\n _ => Err(format!(\"unknown direction: {}\", s)),\n }\n }\n}\n\nfn main() {\n let dir = Direction::from_str(\"NORTH\").unwrap();\n assert_eq!(dir, Direction::North);\n}\n\n\nManual case handling requires explicit to_lowercase().
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n// EnumString uses strum::ParseError by default\n#[derive(Debug, EnumString)]\nenum Animal {\n Dog,\n Cat,\n Bird,\n}\n\nfn main() {\n let result = Animal::from_str(\"Fish\");\n \n // Error type is strum::ParseError\n // It's opaque - implements Display but has no variants you can match\n match result {\n Ok(animal) => println!(\"{:?}\", animal),\n Err(e) => println!(\"Parse error: {}\", e),\n // Parse error: Could not parse Fish\n }\n}\n\n\nEnumString produces strum::ParseError which is opaque.
rust\nuse std::str::FromStr;\n\n#[derive(Debug, PartialEq)]\nenum Animal {\n Dog,\n Cat,\n Bird,\n}\n\n#[derive(Debug)]\nenum AnimalParseError {\n UnknownVariant(String),\n EmptyInput,\n}\n\nimpl std::fmt::Display for AnimalParseError {\n fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n match self {\n AnimalParseError::UnknownVariant(s) => {\n write!(f, \"unknown animal variant: {}\", s)\n }\n AnimalParseError::EmptyInput => write!(f, \"empty input\"),\n }\n }\n}\n\nimpl std::error::Error for AnimalParseError {}\n\nimpl FromStr for Animal {\n type Err = AnimalParseError;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n if s.is_empty() {\n return Err(AnimalParseError::EmptyInput);\n }\n \n match s {\n \"Dog\" => Ok(Animal::Dog),\n \"Cat\" => Ok(Animal::Cat),\n \"Bird\" => Ok(Animal::Bird),\n _ => Err(AnimalParseError::UnknownVariant(s.to_string())),\n }\n }\n}\n\nfn main() {\n let result = Animal::from_str(\"Fish\");\n \n // Can match on specific error variants\n match result {\n Err(AnimalParseError::UnknownVariant(s)) => {\n println!(\"Unknown: {}\", s);\n }\n Err(AnimalParseError::EmptyInput) => {\n println!(\"Empty\");\n }\n Ok(animal) => println!(\"{:?}\", animal),\n }\n}\n\n\nManual FromStr allows custom error types with matchable variants.
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n// EnumString handles simple variants\n#[derive(Debug, EnumString)]\nenum Simple {\n A,\n B,\n C,\n}\n\n// But cannot handle variants with data\n// #[derive(EnumString)]\n// enum Complex {\n// Point(i32, i32), // Not supported\n// Named { x: i32, y: i32 }, // Not supported\n// }\n\n// Manual FromStr can handle variants with data\n#[derive(Debug, PartialEq)]\nenum Complex {\n Point(i32, i32),\n Named { x: i32, y: i32 },\n}\n\nimpl FromStr for Complex {\n type Err = String;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n // Parse \"Point(1,2)\" or \"Named{x:1,y:2}\"\n if s.starts_with(\"Point(\") && s.ends_with(')') {\n let inner = &s[6..s.len()-1];\n let parts: Vec<&str> = inner.split(',').collect();\n if parts.len() == 2 {\n let x: i32 = parts[0].parse().map_err(|_| \"invalid x\")?;\n let y: i32 = parts[1].parse().map_err(|_| \"invalid y\")?;\n return Ok(Complex::Point(x, y));\n }\n }\n \n if s.starts_with(\"Named{\") && s.ends_with('}') {\n let inner = &s[6..s.len()-1];\n let parts: Vec<&str> = inner.split(',').collect();\n if parts.len() == 2 {\n let x: i32 = parts[0]\n .strip_prefix(\"x:\")\n .ok_or(\"missing x\")?\n .parse().map_err(|_| \"invalid x\")?;\n let y: i32 = parts[1]\n .strip_prefix(\"y:\")\n .ok_or(\"missing y\")?\n .parse().map_err(|_| \"invalid y\")?;\n return Ok(Complex::Named { x, y });\n }\n }\n \n Err(format!(\"invalid Complex: {}\", s))\n }\n}\n\nfn main() {\n let point = Complex::from_str(\"Point(10,20)\").unwrap();\n assert_eq!(point, Complex::Point(10, 20));\n}\n\n\nManual FromStr handles complex variants; EnumString does not support them.
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n#[derive(Debug, EnumString)]\nenum Response {\n Ok,\n Error,\n // Default variant for unknown strings\n #[strum(default)]\n Unknown(String),\n}\n\nfn main() {\n let response = Response::from_str(\"Ok\").unwrap();\n println!(\"{:?}\", response); // Ok\n \n // Unknown strings go to default variant\n let response = Response::from_str(\"NotFound\").unwrap();\n println!(\"{:?}\", response); // Unknown(\"NotFound\")\n}\n\n\nEnumString supports #[strum(default)] for catch-all variants.
rust\nuse std::str::FromStr;\n\n#[derive(Debug, PartialEq)]\nenum Response {\n Ok,\n Error,\n Unknown(String),\n}\n\nimpl FromStr for Response {\n type Err = String;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n match s {\n \"Ok\" => Ok(Response::Ok),\n \"Error\" => Ok(Response::Error),\n other => Ok(Response::Unknown(other.to_string())),\n }\n }\n}\n\nfn main() {\n let response = Response::from_str(\"NotFound\").unwrap();\n assert_eq!(response, Response::Unknown(\"NotFound\".to_string()));\n}\n\n\nManual implementation provides full control over default behavior.
rust\nuse strum::EnumString;\nuse serde::Deserialize;\nuse std::str::FromStr;\n\n// EnumString works alongside Serde\n#[derive(Debug, EnumString, Deserialize)]\n#[strum(ascii_case_insensitive)]\n#[serde(rename_all = \"lowercase\")]\nenum Role {\n Admin,\n User,\n Guest,\n}\n\nfn main() {\n // FromStr via EnumString\n let role = Role::from_str(\"admin\").unwrap();\n \n // Serde deserialization\n let json = r#\"\"user\"\"#;\n let role: Role = serde_json::from_str(json).unwrap();\n}\n\n\nEnumString complements Serde's Deserialize for string parsing.
rust\nuse strum::{EnumString, ToString};\nuse std::str::FromStr;\n\n#[derive(Debug, EnumString, ToString)]\nenum Status {\n #[strum(serialize = \"active\")]\n Active,\n \n #[strum(serialize = \"inactive\")]\n Inactive,\n}\n\nfn main() {\n // ToString for serialization\n let status = Status::Active;\n let status_str = status.to_string();\n println!(\"{}\", status_str); // \"active\"\n \n // FromStr via EnumString for parsing\n let parsed = Status::from_str(\"active\").unwrap();\n assert_eq!(parsed, Status::Active);\n}\n\n\nEnumString pairs with ToString for round-trip string conversion.
rust\n// What EnumString generates (simplified)\n\n#[derive(Debug, strum::EnumString)]\nenum Color {\n Red,\n Green,\n}\n\n// Roughly generates:\nimpl std::str::FromStr for Color {\n type Err = strum::ParseError;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n match s {\n \"Red\" => Ok(Color::Red),\n \"Green\" => Ok(Color::Green),\n _ => Err(strum::ParseError::new()),\n }\n }\n}\n\n// Manual implementation gives you control over:\n// 1. Error type (can be custom)\n// 2. Match logic (can be complex)\n// 3. Additional processing (can validate, transform)\n// 4. Data extraction (can parse into variants with fields)\n\n\nEnumString generates standard FromStr implementations.
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n// EnumString generates a match statement\n// O(n) for n variants in practice\n#[derive(Debug, EnumString)]\nenum Small {\n A, B, C,\n}\n\n// Manual can use more efficient lookups if needed\nuse std::str::FromStr;\n\n#[derive(Debug, PartialEq)]\nenum Large {\n A, B, C, D, E, F, G, H, I, J,\n}\n\nimpl FromStr for Large {\n type Err = String;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n // Could use a hash map for large enums\n // Or optimize with hash-based switch\n match s {\n \"A\" => Ok(Large::A),\n \"B\" => Ok(Large::B),\n \"C\" => Ok(Large::C),\n \"D\" => Ok(Large::D),\n \"E\" => Ok(Large::E),\n \"F\" => Ok(Large::F),\n \"G\" => Ok(Large::G),\n \"H\" => Ok(Large::H),\n \"I\" => Ok(Large::I),\n \"J\" => Ok(Large::J),\n _ => Err(format!(\"unknown: {}\", s)),\n }\n }\n}\n\nfn main() {\n // Both are similar in performance for small enums\n // Manual gives control for optimization if needed\n}\n\n\nBoth use pattern matching; manual allows optimization.
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n#[derive(Debug, EnumString)]\n#[strum(ascii_case_insensitive)] // Case-insensitive for all variants\nenum Example {\n // Multiple serialize names\n #[strum(serialize = \"one\", serialize = \"first\")]\n One,\n \n // Different from variant name\n #[strum(serialize = \"two\")]\n Two,\n \n // Default variant for unknown strings\n #[strum(default)]\n Unknown(String),\n \n // Skip this variant (won't parse)\n #[strum(disabled)]\n Internal,\n}\n\nfn main() {\n // One parses from \"one\" or \"first\"\n let one = Example::from_str(\"one\").unwrap();\n let one = Example::from_str(\"first\").unwrap();\n \n // Case-insensitive\n let two = Example::from_str(\"TWO\").unwrap();\n \n // Unknown goes to default\n let unknown = Example::from_str(\"anything\").unwrap();\n \n // Disabled variants are skipped\n let result = Example::from_str(\"Internal\");\n // Goes to default instead\n}\n\n\nEnumString provides rich attribute-based configuration.
rust\n// Use EnumString when:\n\n// 1. Variants map directly to strings\n#[derive(Debug, strum::EnumString)]\nenum Status {\n Active,\n Inactive,\n Pending,\n}\n\n// 2. You need case-insensitive parsing\n#[derive(Debug, strum::EnumString)]\n#[strum(ascii_case_insensitive)]\nenum Color {\n Red, Green, Blue,\n}\n\n// 3. Simple string aliases\n#[derive(Debug, strum::EnumString)]\nenum Role {\n #[strum(serialize = \"admin\")]\n Administrator,\n}\n\n// 4. Default fallback handling\n#[derive(Debug, strum::EnumString)]\nenum Response {\n Ok,\n #[strum(default)]\n Unknown(String),\n}\n\n// 5. Quick implementation, no custom logic needed\n\n\nUse EnumString for straightforward string-to-variant mapping.
rust\nuse std::str::FromStr;\n\n// Use manual FromStr when:\n\n// 1. Variants have associated data\nenum Point {\n Coordinate(i32, i32),\n Named(String),\n}\n\n// 2. Complex parsing logic\nimpl FromStr for Point {\n type Err = String;\n fn from_str(s: &str) -> Result<Self, Self::Err> {\n let parts: Vec<&str> = s.split(',').collect();\n match parts.len() {\n 2 => {\n let x: i32 = parts[0].parse().map_err(|_| \"invalid x\")?;\n let y: i32 = parts[1].parse().map_err(|_| \"invalid y\")?;\n Ok(Point::Coordinate(x, y))\n }\n _ => Err(\"expected 'x,y' format\".into()),\n }\n }\n}\n\n// 3. Custom error types needed\n// 4. Validation beyond string matching\n// 5. Parsing from complex formats (JSON, custom syntax)\n// 6. Runtime computed mappings\n\n\nUse manual FromStr for complex parsing requirements.
rust\nuse strum::EnumString;\nuse std::str::FromStr;\n\n// Use EnumString for simple variants, manual for complex\n\n#[derive(Debug, EnumString)]\nenum Simple {\n A, B, C,\n}\n\n#[derive(Debug, PartialEq)]\nenum Complex {\n Simple(Simple),\n Number(i32),\n}\n\nimpl FromStr for Complex {\n type Err = String;\n \n fn from_str(s: &str) -> Result<Self, Self::Err> {\n // Try parsing as number first\n if let Ok(n) = s.parse::<i32>() {\n return Ok(Complex::Number(n));\n }\n \n // Fall back to simple enum\n let simple = Simple::from_str(s)\n .map_err(|_| format!(\"invalid Complex: {}\", s))?;\n Ok(Complex::Simple(simple))\n }\n}\n\nfn main() {\n let complex = Complex::from_str(\"42\").unwrap();\n assert_eq!(complex, Complex::Number(42));\n \n let complex = Complex::from_str(\"A\").unwrap();\n assert_eq!(complex, Complex::Simple(Simple::A));\n}\n\n\nCombine approaches: EnumString for simple enums, manual for composition.
FromStr implementation\n- Default: variant names as strings (case-sensitive)\n- Attributes for customization: serialize, ascii_case_insensitive, default\n- Error type: opaque strum::ParseError\n- Supports: simple unit variants only\n- Zero boilerplate for common cases\n\nManual FromStr characteristics:\n- Full control over parsing logic\n- Custom error types with matchable variants\n- Support for variants with associated data\n- Complex validation and transformation\n- Computation during parsing\n- Arbitrary string formats\n\nEnumString advantages:\n- Minimal code, derive macro handles everything\n- Declarative attributes for common customizations\n- Consistent error handling across enums\n- Easy to maintain (variants auto-supported)\n- Works well with other strum derives\n\nManual FromStr advantages:\n- Complete control over parsing logic\n- Can parse complex formats\n- Custom, informative error types\n- Support for variants with fields\n- Validation beyond string matching\n- Can use lookups, computations, external data\n\nPerformance: Both generate pattern matching; comparable for simple cases.\n\nError handling: EnumString uses opaque error; manual can use rich error types.\n\nKey insight: EnumString is ideal for the common case where enum variants correspond to string values with straightforward mappingāconfiguration values, status codes, type discriminators. It handles case insensitivity, aliases, and default fallbacks through attributes without boilerplate. Manual FromStr is necessary when parsing requires logic beyond string matching: variants with data, computed values, complex validation, or custom error types. The approaches can coexist: use EnumString for simple enums within larger systems that use manual FromStr for complex types. The strum crate's EnumString reduces boilerplate for the 80% case of string parsing, while manual implementation remains essential for the 20% requiring custom parsing semantics.","path":"/articles/269_strum_enum_string_vs_from_str.md"}}