#invariants #representation #rep #class #data-structures

rep derive

一个小工具,用于表示/类不变性

3 个版本 (破坏性更新)

0.3.0 2020年6月6日
0.2.0 2019年11月5日
0.1.0 2019年11月4日

#invariants 中排名 37

每月下载 25
rep 中使用

MIT 许可证

22KB
334

rep

rep 是一个小工具,允许您轻松地在Rust数据结构中强制执行 表示/类不变性

表示不变性是针对您的数据结构每次突变都必须为真的逻辑断言。例如,在您的GIS应用程序中,您可能有一个如下的rep不变性。

self.lat >= -90.0 && self.lat <= 90 && self.long >= -180.0 && self.long <= 180

使用 rep 强制执行表示不变性非常简单。向数据结构添加不变性只需要两个简单步骤。

  1. 定义正确的表示(通过手动实现或宏实现 CheckRep
  2. 插入运行时检查(通过手动实现或宏实现)

一些示例

我们可以从一个简单的数据结构开始。

use rep::*;

pub struct Line {
    x1: i32,
    y1: i32,
    x2: i32,
    y2: i32
}

可以实现对 CheckRep 特质的实现。这充当了正确表示的定义。

impl CheckRep for Line {
    fn is_correct(&self) -> bool {
        self.x1 != self.x2 && self.y1 != self.y2
    }
}

现在,我们可以使用 #[check_rep] 宏来自动在所有公开的、修改 &mut self 的方法开始和结束处插入对 check_rep 的调用。我们也可以在需要的地方手动调用 check_rep

#[check_rep] // <-- this inserts calls to check_rep at start and end of move_by
impl Line {
    pub fn new() -> Self {
        let new_line = Self {
            x1: -1,
            y1: -1,
            x1: 1,
            y1: 1
        };
        
        new_line.check_rep();
        new_line
    }
    
    pub fn move_by(&mut self, x: i32, y: i32) {
        self.x1 += x;
        self.x2 += x;
        self.y1 += y;
        self.y2 += y;
    }
}

更多示例

对于简单的表示,我们甚至可以通过派生实现来获得 CheckRep 的实现。

#[derive(CheckRep)]
struct Circle {
    x: i32,
    y: i32,
    #[rep(assert_gt = 0)]
    #[rep(assert_le = 2048)]
    r: i32,
}
struct Parser {
    #[rep(assert_default)]
    unclosed_delims: (usize, usize, usize) // this is representing (parens, braces, brackets)
}

我们可以递归检查表示并使用每个字段的自定义函数。

fn is_health_valid(h: u32) -> bool {
    h > 0 && h < 100
}

#[derive(CheckRep)]
struct Player {
    #[rep(check)]
    position: Point,
    #[rep(assert_with = "is_health_valid")]
    health: u32
}

更高级的rep检查可以通过自定义检查来实现。

fn is_health_valid(h: u32) -> bool {
    h > 0 && h < 100
}

#[derive(CheckRep)]
struct Player {
    #[rep(use_custom)]  // indicates that custom code should be used
    #[rep(check)]
    position: Point,
    #[rep(assert_with = "is_health_valid")]
    health: u32
}

impl CustomCheckRep for Line {
    fn c_correctness(&self) -> Result<(), Vec<String>> {
        let mut errors = vec![];
        if self.x2 != self.y2 {
            errors.push(String::from("self.x2 must equal self.y2"));
        }

        if errors.len() == 0 { Ok(()) } else { Err(errors) }
    }
}
struct Player {
    position: Point,
    health: u32
}

impl CheckRep for Player {
    fn correctness(&self) -> Result<(), Vec<String>> {
        let mut errors = vec![];
        // your code here...
        if errors.len() == 0 { Ok(()) } else { Err(errors) }
    }
}

一旦实现了 CheckRep,您可以使用它与宏 #[check_rep#[require_rep#[check_rep

// this adds `check_rep` at start and end of all public mutating methods
#[check_rep]
impl Device {
    pub fn turn_on(&mut self) {}
    // require_rep, ensure_rep, check_rep add to start, end, start and end respectively
    #[require_rep]
    pub fn get_voltage(&mut self, p: Position) {}
    #[ensure_rep]
    pub fn actuate(&mut self, p: Position, v: Voltage) {}
    #[check_rep]
    fn do_something(&self) {}
}

如果有记录器存在,则将记录违反不变性,而不是崩溃。

用法

只需将以下内容添加到您的 Cargo.toml 文件中。

[dependencies]
rep = "0.3.0"

然后,在您的模块中。

use rep::*;

依赖项

~1.5MB
~35K SLoC