Closed teknico closed 4 years ago
I've honestly never seen this error before, at least in the context of Tetra! But I think that's because I've never actually felt the need to use a lifetime on a State
struct before - lifetimes on long-lived structs make it very easy to get yourself tied in knots and I try to avoid them where possible.
Could you give me a bit more info on how exactly you're using lifetimes in your GameState
? That way I can hopefully understand your use case/why you might be getting errors 🙂
This is a Pacman-like game (actually a clone of a precursor from 1979, Head On 2 by Sega-Gremlin). The game field is represented as a matrix of ~900 tiles, each one with a set of static properties. The logic in various parts of the code needs those properties, so I was trying to avoid copying the matrix several times, but have just one instance of it.
The GameState
struct includes (among other things) a Field
and a Car
:
struct GameState {
field: Field,
car: Car,
...
}
The Field
struct includes a TilePropsMap
:
pub struct Field {
pub tile_props_map: TilePropsMap,
...
}
The Car
code has to look at the tile properties, so its struct includes a reference to the TilePropsMap
instance:
pub struct Car {
position: Coords,
direction: Direction,
tile_props_map: &TilePropsMap,
}
When the GameState
creates the Car
, it needs to pass it the reference to the TilePropsMap
instance:
Ok(GameState {
field: field,
car_player: Car::new(
ctx,
player_position,
Direction::Right,
&field.tile_props_map,
),
...
})
And that's where rustc
starts asking for lifetimes. I'd like to put the TilePropsMap
instance some place where I could declare it static
, or with a 'static
lifetime, or both, but I'm not sure how.
@teknico You could use reference-counting (the book):
use std::rc::Rc;
struct GameState {
field: Field,
car: Car,
}
struct TileMap;
struct Field {
map: Rc<TileMap>
}
struct Car {
map: Rc<TileMap>
}
fn main() {
let map = Rc::new(TileMap);
let g = GameState {
field: Field { map: Rc::clone(&map) },
car: Car { map: Rc::clone(&map) }
};
}
Or probably better, the TilePropsMap
could hold an Rc
to its data (tetra does this with some objects, like Texture
).
Or pass the TilePropsMap
reference into the functions that need it, as long as the caller has access to it:
impl Car {
fn move(&mut self, map: &TilePropsMap) {}
}
// Or free function:
fn move_car(car: &mut Car, map: &TilePropsMap) {}
In your original example, you've created a struct that contains a reference to its own memory - this is something that can't be done in safe Rust without some fairly heavy constraints and complex code, and you're usually better off restructuring your code to avoid it.
To understand why, think about what would happen when you move your GameState
from your code into Context::run
- your struct has moved, but the references contained within would still be pointing at the old location, which isn't allowed in Rust. This is probably why you're getting some fairly horrendous error messages!
I would pretty much concur with the suggestions that @rjframe has made - the best way to think about it is in terms of 'who owns the data':
Context
, but they don't really 'own' it, they're just reading/writing its data for a short period of time.Rc
.
Texture
type in Tetra is reference counted, as there's often cases where you might want to store it in multiple places (e.g. multiple animations attached to the same spritesheet).In your original example, you've created a struct that contains a reference to its own memory
IIUC you're referring to my GameState
containing both a Field
and a Car
, and one of the Car
fields is a reference to one of the Field
fields, and that makes the GameState
instance unmovable.
If one struct is the clear owner of the data, and another struct/function just uses that data, then pass it around.
So Car
cannot have that reference as a field, and I need to pass the TilePropsMap
in whenever I call one of the Car
methods that needs it.
If multiple structs 'own' a piece of data, then you may want to use
Rc
.
Otherwise if I really want to keep that reference I need to use Rc
.
I think I get it. Thanks a lot to both of you, @rjframe and @17cupsofcoffee!
(I could have asked on #games-and-graphics on the Rust Community Discord server but I wanted something more async and less transient.)
I'm half-way through writing a game with Tetra (this is my first Rust project). Everything went fine as long as I made clones; now I'm trying to use references, and have to deal with lifetimes. Every time I try to add them I end up in the same place, this:
that I don't quite understand. Places I looked at for explanations:
rustc
command;I'm still none the wiser. Help please? Thanks.