Our project is to make an Intel 8080 emulator that’ll run Space Invaders, and a big part of it is getting the images the emulator is thinking about to show on screen. We decided to use the SDL2 crate because it can handle the display and the keyboard, and it’s pretty low level which is kind of the theme.
The display workflow goes something like this:
- get the SDL2 crate
- initialize SDL
- initialize the video subsystem
- create a window
- create a canvas inside that window
- create a texture creator tied to the canvas
- create one or more textures tied to the texture creator
- draw on the textures
- place the textures on the canvas
- display the canvas
I was able to get this all working with only a few hours of swearing and pleading. Nice! The only problem was, the code was all in main, which is not tidy. No problem, just break that all off into its own module, we’ll make a struct to store all the bits and some functions tied to it to do stuff to it, and everything’ll be neat as a pin!
So I got to work. Got the view module set up, made a struct to hold the canvas and the texture, wrote the initializer code, tied it to main… and errors.
texture_creator is borrowed here
and
^ returns a value referencing data owned by the current function
In order to understand what Rust is complaining about here, it’s important to know one of the major things that make the language special: it strictly controls memory usage using something called lifetimes, which are like a guarantee of how long an object will live. One side effect of note is, depending on what you’re passing and whether or not it implements copy, if you pass a variable to a function or use it in certain ways, it’s bound to that other code until it finishes, and can be killed at the end of processing if you don’t pass it back.
It’s also important to understand that textures being bound to their texture_creator means they’re bound to its lifetime as well.
So, the first error is complaining about texture_creator being borrowed to make two textures, which are then stored in the struct. The second error appeared after initializing the struct, and it means it doesn’t like that we’re storing the textures and leaving the texture_creator behind to die.
Ok…let’s sprinkle some lifetime annotations around to make sure everything stays alive the whole time. No luck. Ok…let’s try using references to the texture_creator, those have different rules for lifetimes. No, after following the change cascade we still have a dead texture_creator, which the compiler does not like. Alright, let’s store the texture_creator in the struct, too, what the heck. No? Still think it’s going to die? Whyyyyyyyyyy
Ok, let’s back up and see if anyone else has run into this. Google says… yes, lots of people have. The solutions put forward seem to settle into two choices: either initialize everything in main, including the texture_creator, then functionalize the rest of the implementation; or enable unsafe_textures to turn off the lifetimes for textures, meaning the dev (me) will have to make sure there aren’t memory leaks.
Ugh. I don’t like those options, surely there’s a way to do this the way I want to that isn’t ugly or perilous?
Nope. There’s a discussion involving one of the devs where they talk about why the SDL2 crate is set up like that, and another where they confirm the two solutions.
Well…smeg.
Of the two choices, the former is less risky and also I’d already written all the code, so that’s our winner (#lazydev). Thankfully, I’d committed the original everything-in-main version, so I checked the file back out and accepted my fate.
This got me thinking about how one decision spawns another, which spawns another, and so on until some logical but completely crappy outcome appears.