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 std::time::{Duration, Instant}; pub struct Ewma { alpha: f64,
A time-decayed moving average in Rust
exponential-smoothing
time-decay
state-machine
Intermediate
8 steps
go
package middleware import ( "net/http"
Wrapping Gin requests in a DB transaction
middleware
transactions
error-handling
Intermediate
8 steps
rust
use std::fs::File; use std::io::{Read, Seek, SeekFrom}; #[derive(Debug)]
Parsing an HTTP Range header in Rust
http-range
parsing
file-io
Intermediate
10 steps
ruby
class Order < ApplicationRecord belongs_to :customer has_many :line_items has_many :products, through: :line_items
How scopes compose in a Rails model
scopes
activerecord
subqueries
Intermediate
7 steps
rust
use rand::distributions::{Alphanumeric, DistString}; use rand::rngs::OsRng; #[derive(Debug, Clone, PartialEq, Eq)]
A newtype wrapper for API tokens in Rust
newtype-pattern
randomness
encapsulation
Intermediate
6 steps
rust
pub fn normalize_path(input: &str) -> String { let is_absolute = input.starts_with('/'); let has_trailing_slash = input.len() > 1 && input.ends_with('/'); let mut stack: Vec<&str> = Vec::new();
Normalizing filesystem paths in Rust
string-processing
stack
path-manipulation
Intermediate
8 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.