troutwine
troutwine

Reputation: 3831

How to create a Quickcheck Arbitrary of a struct containing a reference?

The Rust quickcheck documentation notes that for any type implementing Arbitrary

They must also be sendable and static since every test is run in its own thread using thread::Builder::spawn, which requires the Send + 'static bounds.

If I need to generate data for a struct that contains a reference how do I go about doing that? For instance:

#![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
    }
}

fails with

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`.

This is a somewhat contrived example but it's in the same spirit as the originating problem. The lifetime annotations work out except but under test.

Upvotes: 4

Views: 1522

Answers (1)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65887

You can't do this for two reasons. First, Arbitrary has a 'static bound, which means that the types that implement Arbitrary may not have references, unless their lifetime is 'static. This ensures that instances don't refer to objects that they don't "own".

Second, in order to return a C<'a> where 'a is anything other than 'static, most of the time you'll also need a parameter that contains a reference with the same lifetime parameter (it's not always necessary, e.g. when the field using the lifetime parameter can be initialized later, but that doesn't apply here). Therefore, you'd need a function defined a bit like this:

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

(Note that 'a is defined on the function, not on the impl.)

There are two big problems with this:

  • Arbitrary::arbitrary() returns Self. This means that the function must return the type on which Arbitrary is implemented. Here, however, C<'a> depends on a lifetime parameter defined on the function; C<'a> cannot possibly be the same as the impl target, since that type cannot use that lifetime parameter.
  • Rng::gen() simply calls Rand::rand(), which also returns Self, and thus suffers from the same problem as Arbitrary::arbitrary(). Also, Rand is not implemented for &str (or even for String).

What can you do then? Instead of storing a &str in your struct, you should store a String. This makes your struct 'static, and you can use the implementation of Arbitrary for String to generate test values.

But what if you don't want to use String in your actual application code? You can make your struct generic by accepting either &str or String. There are two traits in the standard library that help you do this: AsRef and Borrow. Here's an example using Borrow:

use std::borrow::Borrow;

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

Now, you can use either C<&str> or C<String>, depending on what's needed. Obviously, you can't implement Arbitrary for C<&str>, but you can implement it for C<String>. Actually, why not implement it for all types that implement 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 }
    }
}

Upvotes: 7

Related Questions