#contract #design #invariants

adhesion

Rust 中设计接触的一组宏。该库的设计灵感来源于 D 的合同编程功能。

5 个版本 (破坏性更新)

使用旧的 Rust 2015

0.5.0 2018 年 3 月 6 日
0.4.0 2017 年 9 月 27 日
0.3.0 2017 年 8 月 16 日
0.2.0 2017 年 7 月 21 日
0.1.0 2017 年 7 月 7 日

#1023 in Rust 模式

MIT 许可证

56KB
1K SLoC

Adhesion

Linux build status Windows build status crates.io latest published version docs.rs latest published version

一组用于 Rust 中设计接触的宏。该库的设计灵感来源于 D 的合同编程功能。以下是一个快速示例

use std::i32;

contract! {
    fn add_one_to_odd(x: i32) -> i32 {
        post(y) {
            assert!(y - 1 == x, "reverse operation did not produce input");
        }
        body {
            x + 1
        }
        pre {
            assert!(x != i32::MAX, "cannot add one to input at max of number range");
            assert!(x % 2 != 0, "evens ain't appropriate here");
        }
    }
}

assert!(add_one_to_odd(3) == 4);
assert!(add_one_to_odd(5) == 6);
assert_that!(add_one_to_odd(2), panics);
assert_that!(add_one_to_odd(i32::MAX), panics);

在上面的示例中,prebody 之前运行,而 post,其返回值绑定到 y,在之后运行。我们还可以使用 double_check 块定义检查,它将在 body 运行前后进行检查

struct Counter {
    count: u32,
    max: u32
}

contract! {
    fn increment_counter(c: &mut Counter) {
        double_check {
            assert!(c.count <= c.max, "counter max has been exceeded");
        }
        body {
            c.count += 1;
        }
    }
}

let mut counter = Counter { count: 0, max: 3 };

macro_rules! assert_incremented_eq {
    ($e: expr) => ({
        increment_counter(&mut counter);
        assert!(counter.count == $e, format!("expected counter to be {}, got {}", $e, counter.count));
    })
}

assert_incremented_eq!(1);
assert_incremented_eq!(2);
assert_incremented_eq!(3);
assert_incremented_eq!(4); // panics!

实际上,上述示例可以替换为 impl 块中的顶级 double_check 块,这样就不需要重复代码即可为每个方法维护不变性

struct Counter {
    count: u32,
    max: u32
}

impl Counter {
    contract! {
        double_check {
            assert!(self.count <= self.max, "counter max has been exceeded");
        }

        fn increment(&mut self) {
            body {
                self.count.checked_add();
            }
        }
    }

}

let mut counter = Counter { count: 0, max: 3 };

macro_rules! assert_incremented_eq {
    ($e: expr) => ({
        counter.increment();
        assert!(counter.count == $e, format!("expected counter to be {}, got {}", $e, counter.count));
    })
}

assert_incremented_eq!(1);
assert_incremented_eq!(2);
assert_incremented_eq!(3);
assert_incremented_eq!(4); // panics!

很酷,对吧?想了解更多关于这个包的详细信息以及你可以用它做什么,请查看文档

常见问题解答

为什么叫“Adhesion”?

这个库被称为“Adhesion”,是为了参考一种称为“粘附性合同”的特定类型的合同,也称为“要么全有要么全无”的合同。编程中的断言绝对是一种“要么全有要么全无”——如果断言失败,你必须修复断言的条件,或者改变断言本身。听起来很合适!

为什么将 D 的 invariant 重命名为 double_check

在v0.2.0版本发布后,@eternaleye在Reddit论坛讨论区中指出,在技术层面上,“不变性”意味着代码中所有操作之间必须严格维护的强烈保证。这种保证并不是D语言中invariant块的行为所提供的,正如@eternaleye提供的链接所示。

语义很重要,尤其是在像基于合同的软件设计这样的系统中,试图引入更多严谨性。因此,D语言中称为invariant的联合前检查和后检查块,在这个库中被称为double_check

许可

本项目根据您的选择,采用MIT许可协议Apache 2.0许可协议双重许可。

  • Adhesion使用了rust-parse-generics项目组件的修改版本。这里的原始版本和修改版本都使用与本项目相同的双重许可。

贡献者

  • @ErichDonGubler,原始作者
  • @dzamlo,为提供各种重要功能提供帮助。
  • @DanielKeep,他在宏中解析和使用泛型以及Adhesion在开发涉及泛型的功能期间提供指导方面做出了巨大贡献。
  • @eternaleye,他带来了安全专业知识,并推动了double_check与D语言中的invariant的分歧。

无运行时依赖

~165KB