#exception #panic #stack #value #helper #pass #return

pass_by_catastrophe

请勿使用此功能

1 个不稳定版本

0.1.0 2022 年 5 月 22 日

#2684Rust 模式

MIT 许可证

11KB
85

灾难传递

这是一个具有两个辅助特性和一个辅助函数的库,用于简洁性,以及用于 DRY 代码的辅助函数。

通过灾难传递的值通过抛出异常返回,并展开栈。如果遇到 panic 时中断程序,则此库将无法正常工作。

这是对参数的工作方式?通过 FnOnce,或者说,相当于 FnOnce,通常是一个 FnOnce 但不一定必须是 FnOnce:一个 DisasterWaitingToHappen 是我创建的类型,因为 Rust 要求在除函数返回类型之外的其他地方使用 ! 类型时,必须启用夜间功能。因此,DisasterWaitingToHappen 事实上是一个 FnOnce() -> !,只是在那个目前还不稳定的位子没有使用 !。由于 ! 可以转换为任何类型,这样的函数也将是一个 FnOnce() -> NeverNever 是一个私有实现细节),并且它们实现了 DisasterWaitingToHappen。基本上,参数是通过传递一个函数,而不是 x,而是传递 || std::panic::panic_any(x) 来传递的。

默认情况下,此包确实需要夜间工具链以使用 never 夜间功能,但功能标志 stable 将使此库使用一个无法实例化的枚举类型。我认为这实际上没有任何区别,因为这不是公共签名的一部分,而且 ! 也可以转换为那种私有类型。

由于展开的栈被类型化为 dyn Any,因此在读取灾难函数的返回值时,必须重新指定其类型。类型推导显然对为什么你要这样做一无所知,并拒绝履行其职责。

  • 当前Rust不允许将impl和普通泛型参数同时用于同一个函数
  • Catastrophe是无类型的,因此为了让泛型函数正常工作,你需要显式指定它们

因此,你无法在泛型函数中使用implDisasterWaitingToHappen。你必须传递显式的F: DisasterWaitingToHappen

  • DisasterWaitingToHappen通常是未命名的闭包,这意味着你不能将它们放在泛型参数的位置
  • Rust不允许省略参数以推断它们,因此你必须指定灾难类型
  • Rust确实允许你指定一个泛型类型为_,表示“请推断”,而且这行得通

因此,泛型灾难函数几乎总是需要与参数数量相等的_泛型参数。

唯一例外是在将值赋给非灾难值时,例如let绑定,或者实际有用的东西。在这种情况下,你可以完全省略泛型,让Rust自己推断。

catastrophe!宏有两个用途

  1. 如果你用某些表达式调用它,这些表达式是灾难函数调用的结果(即!),它将评估该表达式并将其强制转换为给定的类型。
  2. 如果你给它几个DisasterWaitingToHappen(我讨厌当类型名变得如此之长,以至于复数形式实际上是在中间修改某个词时),你必须手动执行它们,并提供它们各自的返回类型。结果是元组。

这两个都是同一个宏的实现,所以如果你想的话,你可以用它来将多个函数调用组合成元组。实际上,DisasterWaitingToHappen只是一个具有灾难函数的特质,该函数恰好被命名为HALT_AND_CATCH_FIRE。因为它们是相同的实现,具有相同的语法,所以你必须调用该函数。一个合适的名字,因为它确实会停止程序执行并开始堆栈回溯。

这个库中的内容可以帮助将以下代码

use std::ops::Add;
use std::panic::UnwindSafe;
use std::panic::panic_any;
use never::Never;

fn add4<Num: Add<Num, Output = Num>, A, B, C, D>(a: A, b: B, c: C, d: D) -> !
where
    Num: 'static + Send,
    A: FnOnce() -> Never + UnwindSafe,
    B: FnOnce() -> Never + UnwindSafe,
    C: FnOnce() -> Never + UnwindSafe,
    D: FnOnce() -> Never + UnwindSafe,
{
    let a = *std::panic::catch_unwind(a).unwrap_err().downcast::<Num>().unwrap();
    let b = *std::panic::catch_unwind(b).unwrap_err().downcast::<Num>().unwrap();
    let c = *std::panic::catch_unwind(c).unwrap_err().downcast::<Num>().unwrap();
    let d = *std::panic::catch_unwind(d).unwrap_err().downcast::<Num>().unwrap();
    panic_any(a + b + c + d);
}

简化为这样

use std::ops::Add;
use std::panic::panic_any;
use pass_by_catastrophe::Catastrophic;
use pass_by_catastrophe::DisasterWaitingToHappen;

fn add4<Num: Catastrophic, A, B, C, D>(a: A, b: B, c: C, d: D) -> !
where
    Num: Add<Num, Output = Num>,
    A: DisasterWaitingToHappen,
    B: DisasterWaitingToHappen,
    C: DisasterWaitingToHappen,
    D: DisasterWaitingToHappen,
{
    let (a, b, c, d) = catastrophe!(
        a.HALT_AND_CATCH_FIRE() => Num,
        b.HALT_AND_CATCH_FIRE() => Num,
        c.HALT_AND_CATCH_FIRE() => Num,
        d.HALT_AND_CATCH_FIRE() => Num,
    );
    panic_any(a + b + c + d);
}

不过,这并不是一个公平的比较,因为下面使用catastrophe!宏的代码将正确处理类型不正确的恐慌,并且它还将正确处理abcd中的任何一个实际上没有恐慌而是返回的情况。顺便说一下,这是一个在tests.rs中的测试

我推荐magic-import这个crate,它允许进一步简化示例的顶部

magic_import::magic!();

fn add4(...) { ... }

哦,还有,你知道最好的部分是什么吗?如果你在程序中传递灾难,然后在某处尝试捕获一个i32,但代码实际上返回了一个i64,那么错误将一直传播到找到寻找i64的灾难着陆区。享受调试吧,混蛋!

依赖关系

~465KB