rust 70 lines · 9 steps

Building a custom error type in Rust

A hand-rolled ConfigError enum implements Display, Error, and From so config parsing can use the `?` operator cleanly.

Explained by highlit
1use std::fmt;
2 
3#[derive(Debug)]
4pub enum ConfigError {
5 Io(std::io::Error),
6 Parse { field: String, source: std::num::ParseIntError },
7 Missing(String),
8}
9 
10impl fmt::Display for ConfigError {
11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12 match self {
13 ConfigError::Io(e) => write!(f, "failed to read config file: {e}"),
14 ConfigError::Parse { field, source } => {
15 write!(f, "invalid value for `{field}`: {source}")
16 }
17 ConfigError::Missing(field) => write!(f, "missing required field `{field}`"),
18 }
19 }
20}
21 
22impl std::error::Error for ConfigError {
23 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
24 match self {
25 ConfigError::Io(e) => Some(e),
26 ConfigError::Parse { source, .. } => Some(source),
27 ConfigError::Missing(_) => None,
28 }
29 }
30}
31 
32impl From<std::io::Error> for ConfigError {
33 fn from(e: std::io::Error) -> Self {
34 ConfigError::Io(e)
35 }
36}
37 
38pub struct Config {
39 pub port: u16,
40 pub workers: u32,
41}
42 
43pub fn load_config(path: &str) -> Result<Config, ConfigError> {
44 let contents = std::fs::read_to_string(path)?;
45 
46 let mut port = None;
47 let mut workers = None;
48 
49 for line in contents.lines() {
50 let Some((key, value)) = line.split_once('=') else {
51 continue;
52 };
53 let key = key.trim();
54 let value = value.trim();
55 let parsed = value.parse().map_err(|source| ConfigError::Parse {
56 field: key.to_string(),
57 source,
58 })?;
59 match key {
60 "port" => port = Some(parsed as u16),
61 "workers" => workers = Some(parsed),
62 _ => {}
63 }
64 }
65 
66 Ok(Config {
67 port: port.ok_or_else(|| ConfigError::Missing("port".into()))?,
68 workers: workers.ok_or_else(|| ConfigError::Missing("workers".into()))?,
69 })
70}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Modeling errors as an enum lets one type carry several distinct failure cases with their own data.
  2. 2Implementing Display, Error, and From turns a custom type into a first-class citizen of Rust's error ecosystem.
  3. 3A From impl is what makes the `?` operator automatically convert lower-level errors into your own type.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a custom error type in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code