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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Modeling failure as an enum lets each error variant carry exactly the context callers need.
- 2Implementing Display and Error makes a custom type behave like any standard error in the ecosystem.
- 3Combining ok_or_else with ? turns an Option into a typed error and propagates it in one line.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/parsing-a-config-file-with-typed-errors-in-rust-explained-rust-76a8/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.