Hello!
I'm using the SDL2 bindings (rust-sdl2) for a project in Rust, and being fairly new to Rust I've run into a bit of an ownership / borrow checker problem. I've been able to kludge around it with some unsafe code, but I am hoping to learn the proper Rust way of dealing with this.
What I want is to have a struct to hold my textures. In rust-sdl2, a Texture is created using a TextureCreator, and must hold a reference to it. A Texture is dropped if the TextureCreator goes out of scope.
My current implementation is to have separate TextureHolder and Renderer structs, where the Renderer holds the SDL context itself (including the TextureCreator), and TextureHolder holds each Texture. I understand I cannot have them in the same struct, as each Texture holds a reference to the TextureCreator, and having struct members refer to other parts of the same struct is not allowed in Rust. (I may, in the future, try using std::pin to work around this.)
However, the issue I run into is that when I load the first Texture into TextureHolder, it borrows the Renderer struct so it can use the TextureCreator contained therein, but it doesn't give it back. Therefore, creating any single Texture takes ownership / borrows the Renderer, and locks rendering completely. I assume the Renderer gets handed to the Texture and used to make the reference that the Texture needs to hold to the TextureCreator.
I am able to work around this by using std::mem::transmute() in an unsafe block to create a new, unblocked Texture, but I appreciate I'm deliberately breaking the Rust ownership model in doing so. Here's a simplified version of the relevant code (which exists inside of a function that borrows renderer):
let mut result = HashMap::new();
for (name, path) in images_to_load {
let texture = renderer.texture_creator.load_texture(path).expect("Could not load texture.");
// Unsafe kludge below
// If this weren't here, the Renderer would be kidnapped by the HashMap
let texture = unsafe{
std::mem::transmute::<_,sdl2::render::Texture<'static>>(texture)
};
result.insert(String::from(name), texture);
}
return result;
I appreciate this may be avoidable if the TextureCreator (and the entire SDL context) was created in main(), and was passed by itself rather than as part of a struct. This seems to be the approach used in a lot of the examples online, which get away with being simple imperative programs. But this is not appropriate for my application - the rendering logic needs to be abstracted and encapsulated, both for architectural reasons and because SDL is unlikely to be the only rendering back-end.
I'm sure there's a simple solution here, as I cannot imagine the rust-sdl2 crate is intended to work in this way, and as someone new to Rust I'm probably just missing something incredibly obvious. What would the best, most idiomatic way of implementing something like this in Rust be?