Как создать Quickcheck Arbitrary структуры, содержащей ссылку?

В документации по быстрой проверке Rust отмечается, что для любого типа реализации Arbitrary

Они также должны быть доступными для отправки и статическими, поскольку каждый тест запускается в своем собственном потоке с использованием thread::Builder::spawn, что требует статических границ Send +.

Если мне нужно сгенерировать данные для структуры, содержащей ссылку, как мне это сделать? Например:

#![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`.

Это несколько надуманный пример, но он в том же духе, что и исходная проблема. Аннотации времени жизни работают, но тестируются.


person troutwine    schedule 03.04.2016    source источник
comment
Вы не можете. Граница 'static означает, что реализации Arbitrary не могут содержать заимствованные данные. Это ограничение может быть снято, как только panic::recover (или любое другое имя, которое оно получит) будет стабилизировано, но я не уверен.   -  person BurntSushi5    schedule 03.04.2016
comment
А, хорошо, ну это довольно определенный ответ. Обсуждение стабилизации находится здесь? Это единственная причина, по которой это ограничение существует, как указано в документации, из-за запуска каждого теста в своем собственном потоке? Если тесты будут проводиться последовательно, будет ли снято ограничение или возникнут дополнительные осложнения?   -  person troutwine    schedule 04.04.2016
comment
Также спасибо за очень полезную библиотеку тестирования! :)   -  person troutwine    schedule 04.04.2016
comment
@troutwine impl Arbitrary for C<'statc> не сработает? В любом случае экземпляр Arbitrary для нестатических данных в любом случае не имеет особого смысла. Рассмотрим вызов g.gen::<&str>(), который не будет компилироваться, поскольку нет реализации Rand для &str (что это может быть за &str, кроме статической строки?).   -  person Mar    schedule 04.04.2016
comment
@Мар, скорее всего, мне нужно снова обратиться к документам. Это мотивировано моим первым неигрушечным проектом на Rust. Что я делаю, так это пишу тест контроля качества для проверки синтаксического анализатора, свойство которого заключается в том, что parse . format должно быть идентификатором, немного копируя синтаксис Haskell. Вам нужно генерировать произвольные экземпляры структур, которые содержат &str. Однако предположим, что это в корне ошибочно, так как они все равно не будут одними и теми же ссылками.   -  person troutwine    schedule 04.04.2016


Ответы (1)


Вы не можете этого сделать по двум причинам. Во-первых, 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, и вы можете использовать реализацию Arbitrary для String для генерации тестовых значений.

Но что, если вы не хотите использовать String в реальном коде приложения? Вы можете сделать свою структуру универсальной, приняв &str или String. В стандартной библиотеке есть два трейта, которые помогут вам сделать это: AsRef и Borrow. Вот пример использования Borrow:

use std::borrow::Borrow;

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

Теперь вы можете использовать либо C<&str>, либо C<String>, в зависимости от того, что вам нужно. Очевидно, что вы не можете реализовать Arbitrary для C<&str>, но вы можете реализовать его для 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 }
    }
}
person Francis Gagné    schedule 03.04.2016
comment
Чрезвычайно полезно! Большое спасибо! - person troutwine; 04.04.2016