Reputation: 3831
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
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