Problem
There are 54+ unwrap()/expect() calls scattered across the codebase, many of which can panic on valid error conditions (file I/O failures, malformed frontmatter, missing schema fields, non-UTF8 filenames). Some have placeholder messages like "oops" or typos ("failred").
Notable examples
settings/mod.rs:61 — fs::read_to_string(...).expect("oops") on template loading
settings/mod.rs:88 — panic!("HOME directory not found") during config init
settings/mod.rs:104-105 — LazyLock with .expect("failred"), no recovery if settings fail
note.rs:113 — unwrap() on Tantivy document field extraction (crashes if schema changes)
walk.rs:5 — entry.file_name().to_str().unwrap() (panics on non-UTF8 filenames)
search/index.rs, search/query.rs — multiple unwrap() on schema field lookups
Recommended approach
1. Define a crate-level error type with thiserror
A NoteError struct already exists but is minimal. Replace it with an enum covering the main failure domains:
#[derive(Debug, thiserror::Error)]
pub enum InkError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Search error: {0}")]
Search(#[from] tantivy::TantivyError),
#[error("Config error: {0}")]
Config(#[from] config::ConfigError),
#[error("Template error: {0}")]
Template(#[from] minijinja::Error),
#[error("{0}")]
Other(String),
}
2. Propagate errors with Result<T, InkError> and ?
Convert functions that currently unwrap() to return Result. This is mechanical — replace .unwrap() with ? and update return types up the call chain. The CLI dispatch layer in cli.rs is the natural place to catch errors and print user-friendly messages before exiting with a non-zero code.
3. Handle the LazyLock settings case
LazyLock closures can't return Result, so the settings init is a special case. Options:
- Use
LazyLock<Result<Settings, InkError>> and handle the error at each access point
- Keep the
expect() but with a descriptive message, since a missing HOME dir is truly unrecoverable
- Initialize settings explicitly in
main() and pass them through, avoiding the global
4. Use anyhow in main() as an alternative
For a CLI tool, anyhow::Result in main() is a pragmatic choice that gives good error display with minimal boilerplate. This can coexist with a typed InkError for library-level code, or replace it entirely if the crate isn't used as a library.
Scope
This is a good candidate for incremental work — each module can be converted independently, starting with the most crash-prone paths (settings init, note loading, search queries).
Problem
There are 54+
unwrap()/expect()calls scattered across the codebase, many of which can panic on valid error conditions (file I/O failures, malformed frontmatter, missing schema fields, non-UTF8 filenames). Some have placeholder messages like"oops"or typos ("failred").Notable examples
settings/mod.rs:61—fs::read_to_string(...).expect("oops")on template loadingsettings/mod.rs:88—panic!("HOME directory not found")during config initsettings/mod.rs:104-105—LazyLockwith.expect("failred"), no recovery if settings failnote.rs:113—unwrap()on Tantivy document field extraction (crashes if schema changes)walk.rs:5—entry.file_name().to_str().unwrap()(panics on non-UTF8 filenames)search/index.rs,search/query.rs— multipleunwrap()on schema field lookupsRecommended approach
1. Define a crate-level error type with
thiserrorA
NoteErrorstruct already exists but is minimal. Replace it with an enum covering the main failure domains:2. Propagate errors with
Result<T, InkError>and?Convert functions that currently
unwrap()to returnResult. This is mechanical — replace.unwrap()with?and update return types up the call chain. The CLI dispatch layer incli.rsis the natural place to catch errors and print user-friendly messages before exiting with a non-zero code.3. Handle the
LazyLocksettings caseLazyLockclosures can't returnResult, so the settings init is a special case. Options:LazyLock<Result<Settings, InkError>>and handle the error at each access pointexpect()but with a descriptive message, since a missing HOME dir is truly unrecoverablemain()and pass them through, avoiding the global4. Use
anyhowinmain()as an alternativeFor a CLI tool,
anyhow::Resultinmain()is a pragmatic choice that gives good error display with minimal boilerplate. This can coexist with a typedInkErrorfor library-level code, or replace it entirely if the crate isn't used as a library.Scope
This is a good candidate for incremental work — each module can be converted independently, starting with the most crash-prone paths (settings init, note loading, search queries).