|
1 | 1 | //! Tiny bailing convenience macros. |
2 | 2 | //! |
3 | 3 | //! 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. |
6 | 6 | //! |
7 | 7 | //! The middle path avoids unwanted panics without the ergonomic challenges of propagating errors with `?`. |
8 | 8 | //! |
9 | | -//! This crate provides the following macro variants: |
| 9 | +//! This crate provides the following macro variants to determine the preferred behavior on failure: |
10 | 10 | //! - [`or_return!`] |
11 | 11 | //! - [`or_return_quiet!`] |
| 12 | +//! - [`or_return_log_once!`] |
12 | 13 | //! - [`or_continue!`] |
13 | 14 | //! - [`or_continue_quiet!`] |
| 15 | +//! - [`or_continue_log_once!`] |
14 | 16 | //! - [`or_break!`] |
15 | 17 | //! - [`or_break_quiet!`] |
| 18 | +//! - [`or_break_log_once!`] |
16 | 19 | //! |
17 | 20 | //! Along with their tiny aliases: |
18 | 21 | //! [`r!`](prelude::r), |
19 | 22 | //! [`rq!`](prelude::rq), |
| 23 | +//! [`ro!`](prelude::ro), |
20 | 24 | //! [`c!`](prelude::c), |
21 | 25 | //! [`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). |
24 | 30 | //! |
25 | 31 | //! The macros support [`Result`], [`Option`], and [`bool`] types out of the box. |
26 | 32 | //! Implement [`IntoResult`] to extend this to other types. |
@@ -350,6 +356,67 @@ macro_rules! or_break_quiet { |
350 | 356 | }; |
351 | 357 | } |
352 | 358 |
|
| 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 | + |
353 | 420 | #[cfg(test)] |
354 | 421 | mod tests { |
355 | 422 | use std::fmt::Debug; |
@@ -436,6 +503,46 @@ mod tests { |
436 | 503 | assert_eq!(bail(Err(()), -1), failure); |
437 | 504 | } |
438 | 505 |
|
| 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 | + |
439 | 546 | #[test] |
440 | 547 | fn c() { |
441 | 548 | fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E> + Copy, inner: T) -> i32 { |
@@ -552,6 +659,63 @@ mod tests { |
552 | 659 | assert_eq!(bail(Err(()), -1), failure); |
553 | 660 | } |
554 | 661 |
|
| 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 | + } |
555 | 719 | #[test] |
556 | 720 | fn b() { |
557 | 721 | fn bail<T: Eq + Debug, E: Debug>(outer: impl IntoResult<T, E> + Copy, inner: T) -> i32 { |
@@ -667,4 +831,62 @@ mod tests { |
667 | 831 | assert_eq!(bail(None, -1), failure); |
668 | 832 | assert_eq!(bail(Err(()), -1), failure); |
669 | 833 | } |
| 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 | + } |
670 | 892 | } |
0 commit comments