1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! Error propagation tracing in Rust.
//!
//! This crate provides [`propagate::Result`], a replacement for the standard
//! library result type that automatically tracks the propagation of error
//! results using the `?` operator.
//!
//!
//! # Examples
//!
//! Consider the following custom error type:
//!
//! ```
//! use std::io;
//!
//! enum MyError {
//!     Unlucky,
//!     Io(io::Error),
//!     TooSmall(u64),
//! }
//!
//! impl From<io::Error> for MyError {
//!     fn from(e: io::Error) -> Self {
//!         Self::Io(e)
//!     }
//! }
//! ```
//!
//! The following two examples show how [`propagate::Result`] might be used for
//! error handling.
//!
//! ## With try blocks
//!
//! This example makes use of [`try` blocks]. This makes the resulting code
//! cleaner compared to not using `try` blocks.
//!
//! ```
//! #![feature(try_blocks)]
//! # use std::io;
//! # enum MyError {
//! #     Unlucky,
//! #     Io(io::Error),
//! #     TooSmall(u64),
//! # }
//! # impl From<io::Error> for MyError {
//! #     fn from(e: io::Error) -> Self {
//! #         Self::Io(e)
//! #     }
//! # }
//! use std::fs::File;
//!
//! fn file_size(path: &str) -> propagate::Result<u64, MyError> {
//!     try {
//!         // The `?` operator coerces `std::result::Result<_, io::Error>`
//!         // into `propagate::Result<_, MyError>`.
//!         let size = File::open(path)?.metadata()?.len();
//!
//!         if size < 1024 {
//!             Err(MyError::TooSmall(size))?
//!         }
//!
//!         size
//!     }
//! }
//!
//! fn maybe_file_size(path: &str) -> propagate::Result<u64, MyError> {
//!     let lucky = (path.len() % 2) == 0;
//!
//!     try {
//!         if !lucky {
//!             Err(MyError::Unlucky)?
//!         }
//!
//!         file_size(path)?
//!     }
//! }
//!
//! # fn main() {
//! #     let result = maybe_file_size("foo.txt");
//! #     match result {
//! #         propagate::Ok(size) => println!("File size: {} KiB", size / 1024),
//! #         propagate::Err(err, trace) => {
//! #             match err {
//! #                 MyError::Unlucky => println!("Not this time!"),
//! #                 MyError::Io(e) => println!("I/O error: {}", e),
//! #                 MyError::TooSmall(size) => println!("File too small: {} bytes", size),
//! #             }
//! #             println!("Return trace: {}", trace);
//! #         }
//! #     }
//! # }
//! ```
//!
//! ## Without try blocks
//!
//! This example is the same as the one above, except it does not make use of
//! [`try` blocks]. This requires a bit more boilerplate, and also requires the
//! user to remember to properly forward results (see [`propagate::Result`] for
//! more information).
//!
//! ```
//! # use std::io;
//! # enum MyError {
//! #     Unlucky,
//! #     Io(io::Error),
//! #     TooSmall(u64),
//! # }
//! # impl From<io::Error> for MyError {
//! #     fn from(e: io::Error) -> Self {
//! #         Self::Io(e)
//! #     }
//! # }
//! use propagate::ErrorTrace;
//! use std::fs::File;
//!
//! fn file_size(path: &str) -> propagate::Result<u64, MyError> {
//!     // The `?` operator coerces `std::result::Result<_, io::Error>`
//!     // into `propagate::Result<_, MyError>`.
//!     let size = File::open(path)?.metadata()?.len();
//!
//!     if size < 1024 {
//!         // Option 1: Coerce a `std::result::Result` to a `propagate::Result`
//!         // using `?`.
//!         Err(MyError::TooSmall(size))?
//!     }
//!     
//!     propagate::Ok(size)
//! }
//!
//! fn maybe_file_size(path: &str) -> propagate::Result<u64, MyError> {
//!     let lucky = (path.len() % 2) == 0;
//!
//!     if !lucky {
//!         // Option 2: Directly construct a `propagate::Result`
//!         // using `ErrorTrace::new()`.
//!         return propagate::Err(MyError::Unlucky, ErrorTrace::new())
//!     }
//!     
//!     // Must remember to surround with `Ok(..?)`.
//!     propagate::Ok(file_size(path)?)
//! }
//! ```
//!
//! See [`propagate::Result`] for more information.
//!
//! [`propagate::Result`]: crate::Result
//! [`try` blocks]: https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html

#![feature(try_trait_v2)]
#![feature(control_flow_enum)]
#![feature(termination_trait_lib)]

// TODO:
// * Add a feature flag to fall back to standard library results.
// * Massage `CodeLocation` and `ErrorTrace` a bit.
// * Improve crate-level docs a bit.
// * Put more thought into the Result interface.
//   - i.e., should more methods preserve the error trace?
// * Put `MyError` into shared example module?

pub mod result;
pub mod trace;

#[doc(inline)]
pub use self::{
    result::Result,
    trace::{CodeLocation, ErrorTrace, Traced},
};

pub use self::result::Result::{Err, Ok};

#[cfg(test)]
mod test;

// Test that all code snippets in README.md compile.
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
pub struct ReadmeDoctests;