rust 47 lines · 9 steps

Parsing a config file with typed errors in Rust

A key=value parser that reports failures as a custom error enum instead of panicking or returning strings.

Explained by highlit
1use std::collections::HashMap;
2use std::fmt;
3 
4#[derive(Debug)]
5pub enum ParseError {
6 MissingSeparator { line: usize, content: String },
7 EmptyKey { line: usize },
8}
9 
10impl fmt::Display for ParseError {
11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12 match self {
13 ParseError::MissingSeparator { line, content } => {
14 write!(f, "line {line}: missing '=' in `{content}`")
15 }
16 ParseError::EmptyKey { line } => write!(f, "line {line}: empty key"),
17 }
18 }
19}
20 
21impl std::error::Error for ParseError {}
22 
23pub fn parse_config(input: &str) -> Result<HashMap<String, String>, ParseError> {
24 let mut config = HashMap::new();
25 
26 for (idx, raw) in input.lines().enumerate() {
27 let line = idx + 1;
28 let trimmed = raw.trim();
29 
30 if trimmed.is_empty() || trimmed.starts_with('#') {
31 continue;
32 }
33 
34 let (key, value) = trimmed.split_once('=').ok_or_else(|| {
35 ParseError::MissingSeparator { line, content: trimmed.to_owned() }
36 })?;
37 
38 let key = key.trim();
39 if key.is_empty() {
40 return Err(ParseError::EmptyKey { line });
41 }
42 
43 config.insert(key.to_owned(), value.trim().to_owned());
44 }
45 
46 Ok(config)
47}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Modeling failure as an enum lets each error variant carry exactly the context callers need.
  2. 2Implementing Display and Error makes a custom type behave like any standard error in the ecosystem.
  3. 3Combining ok_or_else with ? turns an Option into a typed error and propagates it in one line.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing a config file with typed errors in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code