3个版本 (破坏性)

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

#754 in 数据结构

MIT 许可证

10KB
79

rep

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

表示不变性是针对您数据结构的每个变异都必须为真的逻辑断言。例如,在您的GIS应用程序中,您可能有一个针对 LatLong 的以下 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] 宏自动在所有 pub 和修改 &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
约37K SLoC