Skip to content

Commit 979b2ff

Browse files
committed
Add "log-once" macro variants
1 parent 77dd2a5 commit 979b2ff

2 files changed

Lines changed: 238 additions & 10 deletions

File tree

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,32 @@
55
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?style=for-the-badge)](https://github.com/benfrankel/tiny_bail)
66

77
Bailing is an error-handling pattern that takes the middle path between `unwrap` and `?`:
8-
- Compared to `unwrap`: Bail will `return`, `continue`, or `break` instead of panicking.
9-
- Compared to `?`: Bail will log or quietly discard the error instead of propagating it.
8+
- Compared to `unwrap`: Bailing will `return`, `continue`, or `break` instead of panicking.
9+
- Compared to `?`: Bailing will log or quietly discard the error instead of returning it.
1010

1111
The middle path avoids unwanted panics without the ergonomic challenges of propagating errors with `?`.
1212

13-
This crate provides the following macro variants:
13+
This crate provides the following macro variants to determine the preferred behavior on failure:
1414
- [`or_return!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_return.html)
1515
- [`or_return_quiet!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_return_quiet.html)
16+
- [`or_return_log_once!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_return_log_once.html)
1617
- [`or_continue!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_continue.html)
1718
- [`or_continue_quiet!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_continue_quiet.html)
19+
- [`or_continue_log_once!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_continue_log_once.html)
1820
- [`or_break!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_break.html)
1921
- [`or_break_quiet!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_break_quiet.html)
22+
- [`or_break_log_once!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.or_break_log_once.html)
2023

2124
Along with their tiny aliases:
2225
[`r!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.r.html),
2326
[`rq!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.rq.html),
27+
[`ro!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.ro.html),
2428
[`c!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.c.html),
2529
[`cq!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.cq.html),
26-
[`b!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.b.html), and
27-
[`bq!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.bq.html).
30+
[`co!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.co.html),
31+
[`b!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.b.html),
32+
[`bq!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.bq.html), and
33+
[`bo!`](https://docs.rs/tiny_bail/latest/tiny_bail/macro.bo.html).
2834

2935
The macros support `Result`, `Option`, and `bool` types out of the box. You can implement
3036
[`IntoResult`](https://docs.rs/tiny_bail/latest/tiny_bail/trait.IntoResult.html) to extend this to other types.

src/lib.rs

Lines changed: 227 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
11
//! Tiny bailing convenience macros.
22
//!
33
//! Bailing is an error-handling pattern that takes the middle path between `unwrap` and `?`:
4-
//! - Compared to `unwrap`: Bail will `return`, `continue`, or `break` instead of panicking.
5-
//! - Compared to `?`: Bail will log or quietly discard the error instead of propagating it.
4+
//! - Compared to `unwrap`: Bailing will `return`, `continue`, or `break` instead of panicking.
5+
//! - Compared to `?`: Bailing will log or discard the error instead of returning it.
66
//!
77
//! The middle path avoids unwanted panics without the ergonomic challenges of propagating errors with `?`.
88
//!
9-
//! This crate provides the following macro variants:
9+
//! This crate provides the following macro variants to determine the preferred behavior on failure:
1010
//! - [`or_return!`]
1111
//! - [`or_return_quiet!`]
12+
//! - [`or_return_log_once!`]
1213
//! - [`or_continue!`]
1314
//! - [`or_continue_quiet!`]
15+
//! - [`or_continue_log_once!`]
1416
//! - [`or_break!`]
1517
//! - [`or_break_quiet!`]
18+
//! - [`or_break_log_once!`]
1619
//!
1720
//! Along with their tiny aliases:
1821
//! [`r!`](prelude::r),
1922
//! [`rq!`](prelude::rq),
23+
//! [`ro!`](prelude::ro),
2024
//! [`c!`](prelude::c),
2125
//! [`cq!`](prelude::cq),
22-
//! [`b!`](prelude::b), and
23-
//! [`bq!`](prelude::bq).
26+
//! [`co!`](prelude::co),
27+
//! [`b!`](prelude::b),
28+
//! [`bq!`](prelude::bq), and
29+
//! [`bo!`](prelude::bo).
2430
//!
2531
//! The macros support [`Result`], [`Option`], and [`bool`] types out of the box.
2632
//! Implement [`IntoResult`] to extend this to other types.
@@ -350,6 +356,67 @@ macro_rules! or_break_quiet {
350356
};
351357
}
352358

359+
/// A helper macro to unwrap on success, or log the first failure and do something else.
360+
#[doc(hidden)]
361+
#[macro_export]
362+
macro_rules! __unwrap_or_log_once {
363+
($expr:expr, $else:expr) => {
364+
match $crate::IntoResult::into_result($expr) {
365+
::core::result::Result::Ok(x) => x,
366+
::core::result::Result::Err(__err) => {
367+
static SHOULD_LOG: ::core::sync::atomic::AtomicBool =
368+
::core::sync::atomic::AtomicBool::new(true);
369+
if SHOULD_LOG.swap(false, ::core::sync::atomic::Ordering::Relaxed) {
370+
$crate::__log_bail!($expr, __err);
371+
}
372+
$else;
373+
}
374+
}
375+
};
376+
}
377+
378+
/// Unwrap on success, or log the first failure and return.
379+
///
380+
/// Returns [`Default::default()`] unless an initial argument is provided to return instead.
381+
#[macro_export]
382+
macro_rules! or_return_log_once {
383+
($return:expr, $expr:expr $(,)?) => {
384+
$crate::__unwrap_or_log_once!($expr, return $return)
385+
};
386+
387+
($expr:expr $(,)?) => {
388+
$crate::__unwrap_or_log_once!($expr, return ::core::default::Default::default())
389+
};
390+
}
391+
392+
/// Unwrap on success, or log the first failure and continue.
393+
///
394+
/// Accepts an optional 'label as the first argument.
395+
#[macro_export]
396+
macro_rules! or_continue_log_once {
397+
($label:tt, $expr:expr $(,)?) => {
398+
$crate::__unwrap_or_log_once!($expr, continue $label)
399+
};
400+
401+
($expr:expr $(,)?) => {
402+
$crate::__unwrap_or_log_once!($expr, continue)
403+
};
404+
}
405+
406+
/// Unwrap on success, or log the first failure and break.
407+
///
408+
/// Accepts an optional 'label as the first argument.
409+
#[macro_export]
410+
macro_rules! or_break_log_once {
411+
($label:tt, $expr:expr $(,)?) => {
412+
$crate::__unwrap_or_log_once!($expr, break $label)
413+
};
414+
415+
($expr:expr $(,)?) => {
416+
$crate::__unwrap_or_log_once!($expr, break)
417+
};
418+
}
419+
353420
#[cfg(test)]
354421
mod tests {
355422
use std::fmt::Debug;
@@ -436,6 +503,46 @@ mod tests {
436503
assert_eq!(bail(Err(()), -1), failure);
437504
}
438505

506+
#[test]
507+
fn ro() {
508+
fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E>, inner: T) -> i32 {
509+
assert_eq!(or_return_log_once!(outer), inner);
510+
2
511+
}
512+
513+
// Success cases should fall through.
514+
let success = 2;
515+
assert_eq!(bail(true, true), success);
516+
assert_eq!(bail(Some(-1), -1), success);
517+
assert_eq!(bail(Ok::<_, ()>(-1), -1), success);
518+
519+
// Failure cases should return early with the default value.
520+
let failure = 0;
521+
assert_eq!(bail(false, true), failure);
522+
assert_eq!(bail(None, -1), failure);
523+
assert_eq!(bail(Err(()), -1), failure);
524+
}
525+
526+
#[test]
527+
fn ro_with_value() {
528+
fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E>, inner: T) -> i32 {
529+
assert_eq!(or_return_log_once!(1, outer), inner);
530+
2
531+
}
532+
533+
// Success cases should fall through.
534+
let success = 2;
535+
assert_eq!(bail(true, true), success);
536+
assert_eq!(bail(Some(-1), -1), success);
537+
assert_eq!(bail(Ok::<_, ()>(-1), -1), success);
538+
539+
// Failure cases should return early with the provided value.
540+
let failure = 1;
541+
assert_eq!(bail(false, true), failure);
542+
assert_eq!(bail(None, -1), failure);
543+
assert_eq!(bail(Err(()), -1), failure);
544+
}
545+
439546
#[test]
440547
fn c() {
441548
fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E> + Copy, inner: T) -> i32 {
@@ -552,6 +659,63 @@ mod tests {
552659
assert_eq!(bail(Err(()), -1), failure);
553660
}
554661

662+
#[test]
663+
fn co() {
664+
fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E> + Copy, inner: T) -> i32 {
665+
let mut val = 0;
666+
'_a: for _ in 0..2 {
667+
val += 1;
668+
for _ in 0..2 {
669+
val += 1;
670+
assert_eq!(or_continue_log_once!(outer), inner);
671+
val += 1;
672+
}
673+
val += 1;
674+
}
675+
val
676+
}
677+
678+
// Success cases should fall through.
679+
let success = 12;
680+
assert_eq!(bail(true, true), success);
681+
assert_eq!(bail(Some(-1), -1), success);
682+
assert_eq!(bail(Ok::<_, ()>(-1), -1), success);
683+
684+
// Failure cases should continue early to the inner loop.
685+
let failure = 8;
686+
assert_eq!(bail(false, true), failure);
687+
assert_eq!(bail(None, -1), failure);
688+
assert_eq!(bail(Err(()), -1), failure);
689+
}
690+
691+
#[test]
692+
fn co_with_label() {
693+
fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E> + Copy, inner: T) -> i32 {
694+
let mut val = 0;
695+
'_a: for _ in 0..2 {
696+
val += 1;
697+
for _ in 0..2 {
698+
val += 1;
699+
assert_eq!(or_continue_log_once!('_a, outer), inner);
700+
val += 1;
701+
}
702+
val += 1;
703+
}
704+
val
705+
}
706+
707+
// Success cases should fall through.
708+
let success = 12;
709+
assert_eq!(bail(true, true), success);
710+
assert_eq!(bail(Some(-1), -1), success);
711+
assert_eq!(bail(Ok::<_, ()>(-1), -1), success);
712+
713+
// Failure cases should continue early to the outer loop.
714+
let failure = 4;
715+
assert_eq!(bail(false, true), failure);
716+
assert_eq!(bail(None, -1), failure);
717+
assert_eq!(bail(Err(()), -1), failure);
718+
}
555719
#[test]
556720
fn b() {
557721
fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E> + Copy, inner: T) -> i32 {
@@ -667,4 +831,62 @@ mod tests {
667831
assert_eq!(bail(None, -1), failure);
668832
assert_eq!(bail(Err(()), -1), failure);
669833
}
834+
835+
#[test]
836+
fn bo() {
837+
fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E> + Copy, inner: T) -> i32 {
838+
let mut val = 0;
839+
'_a: for _ in 0..2 {
840+
val += 1;
841+
for _ in 0..2 {
842+
val += 1;
843+
assert_eq!(or_break_log_once!(outer), inner);
844+
val += 1;
845+
}
846+
val += 1;
847+
}
848+
val
849+
}
850+
851+
// Success cases should fall through.
852+
let success = 12;
853+
assert_eq!(bail(true, true), success);
854+
assert_eq!(bail(Some(-1), -1), success);
855+
assert_eq!(bail(Ok::<_, ()>(-1), -1), success);
856+
857+
// Failure cases should break early from the inner loop.
858+
let failure = 6;
859+
assert_eq!(bail(false, true), failure);
860+
assert_eq!(bail(None, -1), failure);
861+
assert_eq!(bail(Err(()), -1), failure);
862+
}
863+
864+
#[test]
865+
fn bo_with_label() {
866+
fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E> + Copy, inner: T) -> i32 {
867+
let mut val = 0;
868+
'_a: for _ in 0..2 {
869+
val += 1;
870+
for _ in 0..2 {
871+
val += 1;
872+
assert_eq!(or_break_log_once!('_a, outer), inner);
873+
val += 1;
874+
}
875+
val += 1;
876+
}
877+
val
878+
}
879+
880+
// Success cases should fall through.
881+
let success = 12;
882+
assert_eq!(bail(true, true), success);
883+
assert_eq!(bail(Some(-1), -1), success);
884+
assert_eq!(bail(Ok::<_, ()>(-1), -1), success);
885+
886+
// Failure cases should break early from the outer loop.
887+
let failure = 2;
888+
assert_eq!(bail(false, true), failure);
889+
assert_eq!(bail(None, -1), failure);
890+
assert_eq!(bail(Err(()), -1), failure);
891+
}
670892
}

0 commit comments

Comments
 (0)