unwrap_log/
lib.rs

1//! Non-panicking alternatives to `Option` and `Result` unwrapping, which log at warn level.
2//!
3//! ## Example
4//!
5//! ```rust
6//! use unwrap_log::{OptionExt, ResultExt};
7//! use env_logger::Builder;
8//! use log::LevelFilter::Warn;
9//!
10//! Builder::new().filter_level(Warn).init();
11//!
12//! let x: i32 = None.unwrap_or_default_log();
13//! assert_eq!(x, 0);
14//!
15//! let y: i32 = Err("oops").unwrap_or_default_log();
16//! assert_eq!(y, 0);
17//! ```
18//!
19//! Output:
20//! ```text
21//! [1970-01-01T00:00:00Z WARN  my_crate] src\main.rs:8:23 encountered `None`
22//! [1970-01-01T00:00:00Z WARN  my_crate] src\main.rs:11:30 encountered `Err("oops")`
23//! ```
24#![no_std]
25
26/// Extension trait providing tracing alternatives to `Option` unwrap methods.
27pub trait OptionExt {
28    /// The type of the "present" output, intended to be `T` for a `Option<T>`.
29    type Output;
30    /// Returns the contained `Some` value, or logs at the warn level and returns a default value.
31    fn unwrap_or_default_log(self) -> Self::Output;
32    /// Returns the contained `Some` value, or logs at the warn level and computes a default value from a closure.
33    fn unwrap_or_else_log(self, f: impl FnOnce() -> Self::Output) -> Self::Output;
34    /// Returns the contained `Some` value, or logs at the warn level and returns the provided default.
35    fn unwrap_or_log(self, default: Self::Output) -> Self::Output;
36}
37
38/// Extension trait providing tracing alternatives to `Result` unwrap methods.
39pub trait ResultExt {
40    /// The type of the "successful" output, intended to be `T` for a `Result<T, E>`.
41    type Output;
42    /// Returns the contained `Ok` value, or logs at the warn level and returns a default value.
43    fn unwrap_or_default_log(self) -> Self::Output;
44    /// Returns the contained `Ok` value, or logs at the warn level and computes a default value from a closure.
45    fn unwrap_or_else_log(self, f: impl FnOnce() -> Self::Output) -> Self::Output;
46    /// Returns the contained `Ok` value, or logs at the warn level and returns the provided default.
47    fn unwrap_or_log(self, default: Self::Output) -> Self::Output;
48}
49
50/// Like `ResultExt` for `Result<T, E>`, but doesn't require `E: Debug`.
51///
52/// This is provided for users who want to avoid logging sensitive information,
53/// or who want to slim down their log files.
54pub trait ResultExtNoDbg {
55    /// The type of the "successful" output, intended to be `T` for a `Result<T, E>`.
56    type Output;
57    /// Returns the contained `Ok` value, or logs at the warn level and returns a default value.
58    fn unwrap_or_default_log(self) -> Self::Output;
59    /// Returns the contained `Ok` value, or logs at the warn level and computes a default value from a closure.
60    fn unwrap_or_else_log(self, f: impl FnOnce() -> Self::Output) -> Self::Output;
61    /// Returns the contained `Ok` value, or logs at the warn level and returns the provided default.
62    fn unwrap_or_log(self, default: Self::Output) -> Self::Output;
63}
64
65impl<T: Default> OptionExt for Option<T> {
66    type Output = T;
67
68    #[track_caller]
69    fn unwrap_or_default_log(self) -> T {
70        if let Some(x) = self {
71            x
72        } else {
73            option_error();
74            T::default()
75        }
76    }
77
78    #[track_caller]
79    fn unwrap_or_else_log(self, f: impl FnOnce() -> T) -> T {
80        if let Some(x) = self {
81            x
82        } else {
83            option_error();
84            f()
85        }
86    }
87
88    #[track_caller]
89    fn unwrap_or_log(self, default: T) -> T {
90        if let Some(x) = self {
91            x
92        } else {
93            option_error();
94            default
95        }
96    }
97}
98
99impl<T: Default, E: core::fmt::Debug> ResultExt for Result<T, E> {
100    type Output = T;
101
102    #[track_caller]
103    fn unwrap_or_default_log(self) -> T {
104        match self {
105            Ok(x) => x,
106            Err(err) => {
107                result_error(&err);
108                T::default()
109            }
110        }
111    }
112
113    #[track_caller]
114    fn unwrap_or_else_log(self, f: impl FnOnce() -> T) -> T {
115        match self {
116            Ok(x) => x,
117            Err(err) => {
118                result_error(&err);
119                f()
120            }
121        }
122    }
123
124    #[track_caller]
125    fn unwrap_or_log(self, default: T) -> T {
126        match self {
127            Ok(x) => x,
128            Err(err) => {
129                result_error(&err);
130                default
131            }
132        }
133    }
134}
135
136impl<T: Default, E> ResultExtNoDbg for Result<T, E> {
137    type Output = T;
138
139    #[track_caller]
140    fn unwrap_or_default_log(self) -> T {
141        if let Ok(x) = self {
142            x
143        } else {
144            no_dbg_error();
145            T::default()
146        }
147    }
148
149    #[track_caller]
150    fn unwrap_or_else_log(self, f: impl FnOnce() -> T) -> T {
151        if let Ok(x) = self {
152            x
153        } else {
154            no_dbg_error();
155            f()
156        }
157    }
158
159    #[track_caller]
160    fn unwrap_or_log(self, default: T) -> T {
161        if let Ok(x) = self {
162            x
163        } else {
164            no_dbg_error();
165            default
166        }
167    }
168}
169
170#[cold]
171#[inline(never)]
172#[track_caller]
173fn option_error() {
174    let caller = core::panic::Location::caller();
175    log::warn!("{caller} encountered `None`");
176}
177
178#[cold]
179#[inline(never)]
180#[track_caller]
181fn result_error(err: &dyn core::fmt::Debug) {
182    let caller = core::panic::Location::caller();
183    log::warn!("{caller} encountered `Err({err:?})`");
184}
185
186#[cold]
187#[inline(never)]
188#[track_caller]
189fn no_dbg_error() {
190    let caller = core::panic::Location::caller();
191    log::warn!("{caller} encountered `Err(_)`");
192}