Returning a struct with interdependent fields

Hello,

since this is my first post here I would first say hello to everybody : ). I am completely new to Rust and coming from a python / C background. Rust seems like a cool language to learn so I decided to give it a go. During my last vacation I managed to read the majority of the Rust book and now I wanted to experiment with a toy project. So I decided to create the (n+1)th clone of Snake. I started of with the SDL2 example code and managed to get a very basic implementation to work.

This is when I decided that I want to practice a bit more and tried to restructure the whole program and now I am stuck. I don't want to bore everyone with the lengthy ugly code so I just want to show the part that produces the problems (if anyone is interested just ping me and I can later push it to github ; )).

The first version of the code worked somehow like this

main {
//call sdl library functions to create all the required objects
loop {
      handle events( sdl variables)
      draw stuff ( sdl variables)
  }
}

But now I would like to clean up my main function and create all those SDL specific structs in a function. Something like this:

main {
    config = Config::new();
    sdl_components = initialize(config);
    loop {
          handle events(sdl_components);
          draw stuff (sdl_components);
      }
    }

Here is the "real" relevant code section:

pub struct Sdl2Components<'a> {
    canvas: sdl2::render::WindowCanvas,
    event_pump:  sdl2::EventPump,
    _ttf_context: Sdl2TtfContext,
    font: Option<Font<'a, 'static>>,
}

impl <'a> Sdl2Components<'a> {
    fn new_internal(window_width: u32, window_height: u32) -> Sdl2Components<'a> {
        let sdl_context = sdl2::init().unwrap();
        let  event_pump = sdl_context.event_pump().unwrap();
        let window = sdl_context.video().unwrap().window("snake demo", window_width, window_height)
            .position_centered().build().unwrap();
        let canvas = window.into_canvas().build().unwrap();

        Sdl2Components {
            canvas: canvas,
            event_pump: event_pump,
            _ttf_context: sdl2::ttf::init().map_err(|e| e.to_string()).unwrap(),
            font: None 
        }
    }

    pub fn new(window_width: u32, window_height: u32) -> Sdl2Components<'a> {
        let mut comp = Sdl2Components::new_internal(window_width, window_height);

        comp.font = Some(comp._ttf_context.load_font("/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf", 128).unwrap());
        comp
    }
}

Now I get this compiler error:

error[E0515]: cannot return value referencing local data `comp._ttf_context`
   --> src/main.rs:142:9
    |
141 |         comp.font = Some(comp._ttf_context.load_font("/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf", 128).unwrap());
    |                          ----------------- `comp._ttf_context` is borrowed here
142 |         comp
    |         ^^^^ returns a value referencing data owned by the current function



error[E0505]: cannot move out of `comp` because it is borrowed
   --> src/main.rs:142:9
    |
122 | impl <'a> Sdl2Components<'a> {
    |       -- lifetime `'a` defined here
...
141 |         comp.font = Some(comp._ttf_context.load_font("/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf", 128).unwrap());
    |                          ----------------- borrow of `comp._ttf_context` occurs here
142 |         comp
    |         ^^^^
    |         |
    |         move out of `comp` occurs here
    |         returning this value requires that `comp._ttf_context` is borrowed for `'a`

The problem I have is that I want to return the created font from the init function but it needs to have a reference to the ttf_context, which I would also return. Actually, I don't need to use the ttf_context directly I just want to make it live until I don't need font anymore...

Can somebody give me advice how to do this in rust?

Thanks : )

I uploaded the current state to github: https://github.com/greeenway/snake_in_rust

edit: some formatting fixes and added a github link

Structs can't contain references to any content in themselves.

struct SelfReference<'a> {
    data: Vec<u8>,
    first_vec_element: &u8, // Nope!
}

This is because:

  1. The borrow checker tracks one field at a time, so it wouldn't know that after you replace content of one field other fields become invalid.

  2. Rust assumes it's safe to change address of structs (memcpy them elsewhere), but if a struct borrows from itself, the references would be invalid as soon as its moved to another address.

Solutions for this are:

  • Don't use references, use Rc instead (although in this case SDL says you can't).

  • Instead of storing both owning data and borrows in one struct, put all owned data in one struct, and then later create only-borrowing struct with a view into that data.


In your case you will have to take ttf_context as an argument, and don't own it. This way your font will be referencing the lifetime of the already-borrowed ttf context in outer scope, rather than self-referencing your struct.

3 Likes

See this similar thread.

1 Like

Hey, thanks for your super quick reply. This makes sense somehow. I chose your second solution, first created ttf_context and passed it to the function. Then after adding a couple of lifetime specifiers I will understand one day (hopefully), the borrow checker is satisfied, it works again and my code got slightly cleaner.

I tagged the relevant commit in the repo in case someone struggle onto this in the future is interested:

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.