cucumber-rs / cucumber

Cucumber testing framework for Rust. Fully native, no external test runners or dependencies.
https://cucumber-rs.github.io/cucumber/main
Apache License 2.0
562 stars 69 forks source link

Store data with lifetimes in world #320

Open sdupille opened 7 months ago

sdupille commented 7 months ago

Hey there ! I wrote a lib I want to test with cucumber. My lib use some struct that has some lifetimes, like this :

pub struct Intersection<'a> {
    time: f64,
    object: &'a Shape,
}

And I need to store these kinds of values in cucumber world. So, obvious solution was to create a world object with lifetime, like this :

#[derive(Debug)]
pub enum WorldItem<'a> {
    Tuple(Tuple),
    Double(f64),
    Color(Color),
    Canvas(Canvas),
    PPM(String),
    Matrix(Matrix),
    Ray(Ray),
    Shape(Shape),
    Intersect(Intersection<'a>),
    Interset(Intersections<'a>),
    Hit(Option<Intersection<'a>>),
    Light(PointLight),
    Material(Material),
}

#[derive(Debug, World)]
#[world(init = Self::new)]
pub struct CucumberWorld<'a> {
    all_entries: HashMap<String, WorldItem<'a>>,
}

But it doesn't work, and the message is weird :

error[E0106]: missing lifetime specifier
  --> tests/world/mod.rs:26:12
   |
26 | pub struct CucumberWorld<'a> {
   |            ^^^^^^^^^^^^^ expected named lifetime parameter
   |
help: consider introducing a named lifetime parameter
   |
26 | pub struct CucumberWorld<'a><'a><'a> {
   |                         ++++

I also have a bunch of errors like this :

error: `impl` item signature doesn't match `trait` item signature
  --> tests/world/mod.rs:24:17
   |
24 | #[derive(Debug, World)]
   |                 ^^^^^ found `fn(&'1 CucumberThenCucumberWorld) -> (cucumber::step::Location, fn() -> Regex, for<'a> fn(&'a mut world::CucumberWorld<'1>, cucumber::step::Context) -> Pin<Box<(dyn futures::Future<Output = ()> + 'a)>>)`
   |
  ::: /Users/averell/.cargo/registry/src/index.crates.io-6f17d22bba15001f/cucumber-0.20.2/src/codegen.rs:58:5
   |
58 |     fn inner(&self) -> (step::Location, LazyRegex, Step<W>);
   |     -------------------------------------------------------- expected `fn(&'1 CucumberThenCucumberWorld) -> (cucumber::step::Location, fn() -> Regex, for<'a> fn(&'a mut world::CucumberWorld<'a>, cucumber::step::Context) -> Pin<Box<(dyn futures::Future<Output = ()> + 'a)>>)`
   |
   = note: expected signature `fn(&'1 CucumberThenCucumberWorld) -> (cucumber::step::Location, fn() -> Regex, for<'a> fn(&'a mut world::CucumberWorld<'a>, cucumber::step::Context) -> Pin<Box<(dyn futures::Future<Output = ()> + 'a)>>)`
              found signature `fn(&'1 CucumberThenCucumberWorld) -> (cucumber::step::Location, fn() -> Regex, for<'a> fn(&'a mut world::CucumberWorld<'1>, cucumber::step::Context) -> Pin<Box<(dyn futures::Future<Output = ()> + 'a)>>)`
   = help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait`
   = help: verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output
   = note: this error originates in the derive macro `World` (in Nightly builds, run with -Z macro-backtrace for more info)

How can I create a world with lifetime ? Is there a way ? If not, any workaround ?

tyranron commented 7 months ago

@sdupille while we technically can make World macros desugaring supporting lifetimes in World to some extent, it's still not applicable by the whole design.

Let's leave concrete types for a moment, and look at the bigger picture here:

So, there can be several ways to deal with your problem:

  1. Either put the source the Intersection<'a>/WorldItem<'a> point to into the World too, making it self-referential. Crates like owning_ref or ouroboros should help here.
  2. Or, for tests, provide an owning variant of your Intersection<'a> type (i.e. IntersectionOwned) and use it.
  3. Or, if you're absolutely-absolutely-absolutely sure that the source, the Intersection<'a> points to, overlives the World, created by the framework on demand and being Droped who-knows-where (may be at the end of the program), you may unsafe { mem::transmute() } lifetime into a 'static one.