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