【问题标题】:How to create a Quickcheck Arbitrary of a struct containing a reference?如何创建包含引用的结构的 Quickcheck Arbitrary?
【发布时间】:2016-04-03 20:32:55
【问题描述】:

Rust quickcheck documentation 指出,对于任何实现 Arbitrary 的类型

它们还必须是可发送的和静态的,因为每个测试都使用 thread::Builder::spawn 在其自己的线程中运行,这需要 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`.

这是一个有些人为的例子,但它与原始问题的精神相同。生命周期注释除了在测试中之外有效。

【问题讨论】:

  • 你不能。 'static 绑定意味着 Arbitrary 的实现不能包含借用的数据。一旦panic::recover(或任何最终名称)稳定下来,这个限制可能会被解除,但我不确定。
  • 啊,好吧,这是非常明确的答案。稳定讨论是here?由于在自己的线程中运行每个测试,因此存在此限制的唯一原因是文档所述吗?如果测试是连续运行的,会取消限制还是会出现后续并发症?
  • 另外,感谢您提供的超级有用的测试库! :)
  • @troutwine impl Arbitrary for C&lt;'statc&gt; 不工作吗?在任何情况下,非静态数据的Arbitrary 实例都没有多大意义。考虑对g.gen::&lt;&amp;str&gt;() 的调用,它不会编译,因为&amp;str 没有实现Rand(除了静态字符串之外,&str 还能是什么?)。
  • @Mar 很可能我需要再次访问文档。这是由我的第一个非玩具 Rust 项目推动的。我正在做的是编写 QC 测试来验证解析器,属性是 parse . format 应该是身份,稍微抄袭 Haskell 语法。需要生成包含 &str 的任意结构实例。但是,假设这从根本上是有缺陷的,因为它们无论如何都不会是 same 引用。

标签: rust quickcheck


【解决方案1】:

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

其次,为了返回C&lt;'a&gt;,其中'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&lt;'a&gt; 依赖于函数上定义的生命周期参数C&lt;'a&gt; 不可能与 impl 目标相同,因为该类型不能使用该生命周期参数。
  • Rng::gen() 只是调用了Rand::rand(),它也返回了Self,因此遇到了与Arbitrary::arbitrary() 相同的问题。此外,Rand 未针对 &amp;str(甚至对于 String)实现。

那你能做什么?与其在结构中存储&amp;str,不如存储String。这使您的结构'static,您可以使用Arbitrary 的实现String 来生成测试值。

但是如果您不想在实际应用程序代码中使用String 怎么办?您可以通过接受&amp;strString 来使您的结构通用。标准库中有两个特性可以帮助您做到这一点:AsRefBorrow。这是一个使用Borrow的示例:

use std::borrow::Borrow;

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

现在,您可以根据需要使用C&lt;&amp;str&gt;C&lt;String&gt;。显然,你不能为C&lt;&amp;str&gt; 实现Arbitrary,但你可以为C&lt;String&gt; 实现它。其实,为什么不对所有实现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 }
    }
}

【讨论】:

  • 非常有帮助!非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-24
  • 1970-01-01
相关资源
最近更新 更多