14个版本
| 0.6.3 | 2022年3月13日 |
|---|---|
| 0.6.2 | 2021年7月21日 |
| 0.6.0 | 2020年9月5日 |
| 0.4.0 | 2020年5月1日 |
| 0.3.0 | 2019年7月20日 |
#126 在 进程宏
13,813 每月下载量
在 54 个 crates(21 个直接)中使用
69KB
1K SLoC
设计-by-合同 for Rust
使用“合同”注释函数和方法,使用 不变性、前置条件 和 后置条件。
设计-by-合同 是一种流行的通过形式接口规范增强代码的方法。这些规范用于通过在运行时检查它们来提高代码的正确性。
pub struct Library {
available: HashSet<String>,
lent: HashSet<String>,
}
impl Library {
fn book_exists(&self, book_id: &str) -> bool {
self.available.contains(book_id)
|| self.lent.contains(book_id)
}
#[debug_requires(!self.book_exists(book_id), "Book IDs are unique")]
#[debug_ensures(self.available.contains(book_id), "Book now available")]
#[ensures(self.available.len() == old(self.available.len()) + 1)]
#[ensures(self.lent.len() == old(self.lent.len()), "No lent change")]
pub fn add_book(&mut self, book_id: &str) {
self.available.insert(book_id.to_string());
}
#[debug_requires(self.book_exists(book_id))]
#[ensures(ret -> self.available.len() == old(self.available.len()) - 1)]
#[ensures(ret -> self.lent.len() == old(self.lent.len()) + 1)]
#[debug_ensures(ret -> self.lent.contains(book_id))]
#[debug_ensures(!ret -> self.lent.contains(book_id), "Book already lent")]
pub fn lend(&mut self, book_id: &str) -> bool {
if self.available.contains(book_id) {
self.available.remove(book_id);
self.lent.insert(book_id.to_string());
true
} else {
false
}
}
#[debug_requires(self.lent.contains(book_id), "Can't return a non-lent book")]
#[ensures(self.lent.len() == old(self.lent.len()) - 1)]
#[ensures(self.available.len() == old(self.available.len()) + 1)]
#[debug_ensures(!self.lent.contains(book_id))]
#[debug_ensures(self.available.contains(book_id), "Book available again")]
pub fn return_book(&mut self, book_id: &str) {
self.lent.remove(book_id);
self.available.insert(book_id.to_string());
}
}
属性
此crate公开了 requires、ensures 和 invariant 属性。
requires在函数/方法执行之前进行检查。ensures在函数/方法运行完成后进行检查invariant在函数/方法运行之前和之后都进行检查。
此外,所有这些属性都有不同“模式”的版本。请参阅下面的 模式部分。
对于 trait 和 trait impl,可以使用 contract_trait 属性。
更具体的信息可以在crate文档中找到。
伪函数和运算符
old() 函数
此crate提供的独特功能之一是 old() 伪函数,允许在函数调用之前使用参数的值进行检查。这仅适用于 ensures 属性。
#[ensures(*x == old(*x) + 1, "after the call `x` was incremented")]
fn incr(x: &mut usize) {
*x += 1;
}
-> 运算符
对于更复杂的函数,使用逻辑蕴涵表达行为可能很有用。因为Rust没有蕴涵运算符,此crate为此目的添加了此运算符。
#[ensures(person_name.is_some() -> ret.contains(person_name.unwrap()))]
fn geeting(person_name: Option<&str>) -> String {
let mut s = String::from("Hello");
if let Some(name) = person_name {
s.push(' ');
s.push_str(name);
}
s.push('!');
s
}
此运算符是右结合的。
注意:由于 syn 的设计,添加需要解析的自定义操作符比较棘手,因此这个crate通过重写 TokenStream 来实现。重写方法是将表达式分为两部分:一部分在 -> 操作符左侧,另一部分在右侧。这意味着 if a -> b { c } else { d } 不会生成预期的代码。可以通过使用括号或花括号进行显式分组来避免这种情况。
模式
所有属性(requires,ensures,invariant)都有 debug_* 和 test_* 版本。
-
debug_requires/debug_ensures/debug_invariant内部使用debug_assert!而不是assert! -
test_requires/test_ensures/test_invariant使用一个if cfg!(test)来保护assert!。这通常用于表示与“慢但明显正确”的替代实现或检查的等价性。例如,一个归并排序的实现可能看起来像这样
#[test_ensures(is_sorted(input))] fn merge_sort<T: Ord + Copy>(input: &mut [T]) { // ... }
设置
要安装最新版本,请将 contracts 添加到 Cargo.toml 文件的依赖项部分。
[dependencies]
contracts = "0.6.3"
然后,您可以通过在所有计划使用合约属性的文件中添加 use contracts::*; 来将所有过程宏引入作用域。
或者使用“旧式”导入宏,使它们在整个项目中可用。
#[macro_use]
extern crate contracts;
配置
此crate公开了一些功能标志,用于配置断言行为。
disable_contracts- 禁用所有检查和断言。override_debug- 将所有合约(除test_之外)更改为debug_*版本override_log- 如果条件违反,将所有合约(除test_之外)更改为log::error!()调用。不会发生中止。mirai_assertions- 而不是常规的 assert! 样式宏,生成由 MIRAI 静态分析器使用的宏。有关此使用的更多文档,请参阅 MIRAI 仓库。
待办事项
- 为特质实现更多合约。
- 添加一个类似于SPARK的静态分析器,以在项目范围内使用合约进行静态断言。
依赖项
~1.5MB
~35K SLoC