如何创建一个包含引用的结构体的Quickcheck任意值?

3

Rust的quickcheck文档指出,任何实现Arbitrary的类型都必须是可发送和静态的,因为每个测试都在自己的线程中运行,使用thread::Builder::spawn,这需要Send + 'static约束。

由于需要满足Send + 'static约束,因此如果我需要为包含引用的结构体生成数据,该怎么做呢?例如:

#![cfg_attr(test, feature(plugin))]
#![cfg_attr(test, plugin(quickcheck_macros))]

#[cfg(test)]
extern crate quickcheck;

#[cfg(test)]
use quickcheck::{Arbitrary,Gen};

#[allow(dead_code)]
#[derive(Debug,Clone)]
pub struct C<'a> {
    s: &'a str,
    b: bool
}

#[cfg(test)]
impl<'a> Arbitrary for C<'a> {
    fn arbitrary<G: Gen>(g: &mut G) -> C<'a> {
        let s = g.gen::<&str>();
        C{s: s, b: (s.len() > 0)}
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[quickcheck]
    fn len_checks_out(c: C) -> bool {
        (c.s.len() > 0) == c.b
    }
}

失败原因:

cargo test
   Compiling qcq v0.1.0 (file:///Users/blt/projects/us/troutwine/qcquestion)
src/lib.rs:18:10: 18:19 error: the type `C<'a>` does not fulfill the required lifetime [E0477]
src/lib.rs:18 impl<'a> Arbitrary for C<'a> {
                       ^~~~~~~~~
note: type must outlive the static lifetime
error: aborting due to previous error
Build failed, waiting for other jobs to finish...
error: Could not compile `qcq`.

这是一个有点牵强的例子,但它与原始问题的精神相同。生命周期注释已经解决了除了测试之外的所有问题。

3
抱歉,我只能用英语进行回答。以下是翻译的结果:您无法这样做。 'static 绑定意味着 Arbitrary 的实现不能包含借用数据。一旦 panic::recover(或最终确定的名称)稳定下来,可能会放宽此限制,但我不确定。 - BurntSushi5
啊,好的,那是一个非常明确的答案。稳定性讨论在这里(https://github.com/rust-lang/rust/issues/27719)?这个限制存在的唯一原因是文档所述,因为每个测试都在自己的线程中运行吗?如果测试按顺序运行,这个限制会被解除吗?还是会有后续的复杂情况? - troutwine
另外,感谢您提供超级有用的测试库! :) - troutwine
@troutwine 不会 impl Arbitrary for C<'statc> 有效吗?无论如何,对于非静态数据的 Arbitrary 实例都没有太多意义。请考虑调用 g.gen::<&str>(),由于没有 &strRand 实现(除了静态字符串之外,它可能是什么?),因此不会编译。 - Mar
@Mar 很可能我需要再次查阅文档。这是由我的第一个非玩具 Rust 项目所激发的。我正在编写 QC 测试来验证解析器,属性是 parse . format 应该是恒等式,有点借鉴 Haskell 语法。需要生成包含 &str 的任意结构实例。虽然它们不会是相同的引用,但假设这基本上是有缺陷的。 - troutwine
1个回答

6
您不能这样做有两个原因。首先,Arbitrary 有一个 'static 绑定,这意味着实现 Arbitrary 的类型可能没有引用,除非它们的生命周期是 'static。这确保了实例不会引用它们不“拥有”的对象。

其次,为了返回一个 C<'a>,其中 'a 不是 'static,大多数情况下您还需要一个包含具有相同生命周期参数的引用的参数(不总是必要的,例如当使用生命周期参数的字段可以稍后初始化时,但这不适用于此处)。因此,您需要定义一个类似于以下方式的函数:

fn arbitrary<'a, G: Gen>(g: &'a mut G) -> C<'a> {
    let s = g.gen::<&str>();
    C { s: s, b: (s.len() > 0) }
}

需要注意的是,'a 是在函数上定义的,而不是在 impl 上定义的。

这里有两个大问题:

  • Arbitrary::arbitrary() 返回 Self。这意味着函数必须返回实现了 Arbitrary 的类型。然而,在这里,C<'a> 取决于在函数上定义的生命周期参数;C<'a> 不可能与 impl 目标相同,因为该类型不能使用该生命周期参数。
  • Rng::gen() 简单地调用 Rand::rand(),它也返回 Self,因此遇到与 Arbitrary::arbitrary() 相同的问题。此外,Rand 没有针对 &str(甚至没有针对 String)进行实现。
那么你可以怎么做呢?不要在你的结构体中存储&str,而应该存储String。这样可以使你的结构体变成'static,并且你可以使用String的实现来生成测试值。
但是如果你不想在实际应用代码中使用String,你可以通过接受&strString来使你的结构体成为通用的。标准库中有两个特质可以帮助你做到这一点:AsRefBorrow。以下是使用Borrow的示例:
use std::borrow::Borrow;

#[derive(Debug, Clone)]
pub struct C<S: Borrow<str>> {
    s: S,
    b: bool
}

现在,您可以使用C<&str>C<String>,具体取决于需要什么。显然,您无法为C<&str>实现Arbitrary,但是您可以为C<String>实现它。实际上,为什么不为所有实现Arbitrary的类型实现它呢?
impl<S: Borrow<str> + Arbitrary> Arbitrary for C<S> {
    fn arbitrary<G: Gen>(g: &mut G) -> C<S> {
        let s: S = Arbitrary::arbitrary(g);
        let b = s.borrow().len() > 0;
        C { s: s, b: b }
    }
}

非常有帮助!非常感谢! - troutwine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接