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?