Piston and Textures, lifetime error


#1

I have been experimenting with Piston lately. I am trying to create a window, load some .png images and print them.

I tried with this code:

extern crate piston;
extern crate graphics;
extern crate piston_window;

use piston_window::*;

pub struct GameView<G: Graphics> {
	pub grass: G::Texture,
}

pub fn game_piston(game: game::Game) {
	let opengl = OpenGL::V3_2;

	// Create a Piston window.
	let size = (WORLD_SIZE as u32) * LAND_SIZE_PX;
	let window_settings: WindowSettings = WindowSettings::new("tbs-piston", [size, size])
		// .fullscreen(true)
		.opengl(opengl)
		.exit_on_esc(true);
	let mut window: PistonWindow = PistonWindow::build_from_window_settings(&window_settings)
		.unwrap_or_else(|e| panic!("Failed to build PistonWindow: {}", e))
		.lazy(true);

	// Create a new game and run it.
	let mut game_controller = GameController {
		game,
		select_mode: None,
	};

	let game_view = GameView {
		select: Texture::from_path(
			&mut window.factory.clone(),
			Path::new("resources/grass.png"),
			Flip::None,
			&TextureSettings::new()
		).expect("Expected"),
	};

	while let Some(e) = window.next() {
		window.draw_2d(&e, |c, g| {
			game_view.draw(&game_controller, &c, g);
		});
	}
}

but I get this, totally misterious to me, error from the compiler:

error[E0481]: lifetime of function argument does not outlive the function call
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^
    |
note: the function argument is only valid for the anonymous lifetime #3 defined on the body at 297:22
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^

error[E0486]: type of expression contains references that are not valid during the expression: `[closure@src/interface_piston/mod.rs:297:22: 299:4 game_view:&interface_piston::GameView<gfx_graphics::back_end::GfxGraphics<'_, gfx_device_gl::Resources, gfx_device_gl::command::CommandBuffer>>, game_controller:&interface_piston::GameController]`
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^
    |
note: type is only valid for the anonymous lifetime #3 defined on the body at 297:22
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^

error[E0486]: type of expression contains references that are not valid during the expression: `interface_piston::GameView<gfx_graphics::back_end::GfxGraphics<'_, gfx_device_gl::Resources, gfx_device_gl::command::CommandBuffer>>`
   --> src/interface_piston/mod.rs:235:18
    |
235 |   	let game_view = GameView {
    |  __________________^
236 | | 		// font: Font::new(ctx, "/DejaVuSerif.ttf", 12).expect("Expected"),
237 | | 			select: Texture::from_path(
238 | | 			&mut window.factory.clone(),
...   |
278 | | 		).expect("Expected"),
279 | | 			};
    | |____^
    |
note: type is only valid for the anonymous lifetime #3 defined on the body at 297:22
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^

error[E0488]: lifetime of variable does not enclose its declaration
   --> src/interface_piston/mod.rs:235:6
    |
235 | 	let game_view = GameView {
    | 	    ^^^^^^^^^
    |
note: the variable is only valid for the anonymous lifetime #3 defined on the body at 297:22
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^

error[E0495]: cannot infer an appropriate lifetime for capture of `game_view` by closure due to conflicting requirements
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #3 defined on the body at 297:22...
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^
note: ...so that the reference type `&interface_piston::GameView<gfx_graphics::back_end::GfxGraphics<'_, gfx_device_gl::Resources, gfx_device_gl::command::CommandBuffer>>` does not outlive the data it points at
   --> src/interface_piston/mod.rs:297:3
    |
297 | / 		window.draw_2d(&e, |c, g| {
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |____^
note: but, the lifetime must be valid for the expression at 297:22...
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^
note: ...so type `[closure@src/interface_piston/mod.rs:297:22: 299:4 game_view:&interface_piston::GameView<gfx_graphics::back_end::GfxGraphics<'_, gfx_device_gl::Resources, gfx_device_gl::command::CommandBuffer>>, game_controller:&interface_piston::GameController]` of expression is valid during the expression
   --> src/interface_piston/mod.rs:297:22
    |
297 |   		window.draw_2d(&e, |c, g| {
    |  ______________________^
298 | | 			game_view.draw(&game_controller, &c, g);
299 | | 		});
    | |___^

What I am trying to do should be totally trivial, and it should not require extra dependencies like opengl_graphics. What am I doing wrong?


#2

Is there more code behind this? For example, where’s GameView::draw?


#3

There is, and quite a lot too, but I think this is the code where the error lies. It’s not a MWE, but it’s not far from that.

A further note: if I move game_view inside the window.draw_2d argument, it does work. So I guess it’s some problem about lifetimes:

	while let Some(e) = window.next() {
		window.draw_2d(&e, |c, g| {
			let game_view = GameView {
				select: Texture::from_path(
					&mut window.factory.clone(),
					Path::new("resources/grass.png"),
					Flip::None,
					&TextureSettings::new()
				).expect("Expected"),
			};
			game_view.draw(&game_controller, &c, g);
		});
	}

Though, this (I guess) would mean creating game_view anew for every window’s event, which should be unnecessary and a waste of resources.


#4

Yeah, it doesn’t like something about capturing GameView by reference inside the closure type. In particular, it seems to be concerned about the GfxGraphics (which has a lifetime parameter) that’s inside the GameView. So the compiler is basically trying to define:

struct Closure<'a, 'b> {
    game_view: &'a GameView<GfxGraphics<'__, ...>>,
    game_controller: &'b GameController
}

What is the signature of GameView::draw though? If it takes a &mut self then the above closure will be capturing game_view as a &'a mut GameView<...> . My hunch it’s actually this latter case. If GfxGraphics itself has mutable references, then it might not like the fact that the closure could potentially store something short lived into it.

So something in how these lifetimes relate is not to the compiler’s liking, but it’s hard to know without seeing a bit more of the signatures.


#5

Here’s the signature. I guess the body of the function is irrelevant, so I’m cutting it

impl<G: Graphics> GameView<G> {
	pub fn draw(&self, controller: &GameController, context: &Context, g: &mut G) {
		// stuff...
	}
}

#6

Ok, so I think I know the gist of the problem but not the solution :slight_smile:.

It boils down to type inference - the G: Graphics bound isn’t type resolved until the closure is invoked and the &mut G is provided. Without this call, GameView is not a known type because G: Graphics isn’t known.

When this method is called, the G is GfxGraphics<'a, ...>. That 'a lifetime is not associated in any way with your GameView (as far as the compiler is concerned). When you create the GameView inside the closure, the compiler sees the type of G and knows the exact type of GameView<G>. There’s also no reference to GameView so that issue is side-stepped. However, if you did take a reference to that GameView creates inside the closure I suspect it would work because it would know that 'a in GfxGraphics outlives the GameView.

Unfortunately, as mentioned, I’m not sure what the best solution is. Maybe there’s a way to force the GfxGraphics to be known before the closure, and then as long as that graphics lived longer than GameView it should work.


#7

I found a workaround (kind of). I just replaced the generic G: Graphics with G2d and G::Texture with G2dTexture.

The reason why I was using generics is because I wanted to maintain the code backend-agnostic, as reccommended by Piston’s documentation. Though, G2d is already a wrapper around the backend, so it was probably intended to be used directly.

Of course, technically, this is not a “real” solution to the problem I described before. It works for me, though, so I’ll be happy with it.


#8

Ok, nice!

Yeah, I think you fell into a dark corner of Rust with that generic version (exacerbated by GfxGraphics having a lifetime parameter).