Rust-sdl2 - Does not live long enought - fighting the borrow checher


#1

Hey everyone,
Trying to use rust-sdl2.
I am trying to structure my code but I am having an issue. I create a ttf_context but I can’t pass it around to create fonts.
Here is the code:

extern crate sdl2;

use sdl2::ttf;

static FONT_FILE: &'static str = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf";

struct Writer<'a> {
  font: ttf::Font<'a>
}

impl<'a> Writer<'a> {
  fn new(ttf_context: &ttf::Sdl2TtfContext) -> Writer {
    Writer {
      font: ttf_context.load_font(FONT_FILE, 24).unwrap(),
    }
  }
}

struct GfxSubsystem<'a> {
  sdl_context: sdl2::Sdl,
  writer: Writer<'a>,
  ttf_context: ttf::Sdl2TtfContext,
}

impl<'a> GfxSubsystem<'a> {
  fn new() -> GfxSubsystem<'a> {
    let sdl_context = sdl2::init().unwrap();
    let ttf_context = sdl2::ttf::init().unwrap();
    let writer = Writer::new(&ttf_context);
                              ^^^^^^^^^^^ does not live long enough
    GfxSubsystem {
      sdl_context: sdl_context,
      writer: writer,
      ttf_context: ttf_context,
    }
  }
}

fn main() {
  let gss = GfxSubsystem::new();
}

Why ttf_context does not live long enough? It is saved in the GfxSubsystem struct which itself owns a Writer struct so there should be no problem. And anyway, I only use temporarily in Writer to create a Font which lifetime is linked to the Writer struct lifetime.

Any help is appreciated…

Luke


#2

Not sure the answer to your particular problem, but if the eventual goal is just to write text from a font file to the screen, Piston can abstract that for you.

Here is an example that draws Hello World! to the screen: https://github.com/PistonDevelopers/piston-examples/blob/master/src/hello_world.rs


#3

Thanks for your answer, but I am doing many things with the SDL already, I’d rather not switch library on the first difficulty :slight_smile:. There must be something I do wrong, but not sure what…


#4

I think the heart of the problem here is that the GfxSubsystem::new() function conjures up a lifetime without borrowing from anything. I think you may have to change new so that it borrows the sdl_context and the ttf_context.


#5

But you may have an additional difficulty because your GfxSubsystem will store a reference to the ttf_context as well as a writer which contains a reference to the ttf_context. In my experience trying to store two references to the same data in a struct can cause problems with the borrow checker.


#6

Unfortunately, Rust references can point only to something deeper in the call-stack (or to local variables in the same function, you can think of them as lying deeper too). Therefore, referring to another field of a struct using bare & is just impossible in Rust. (If you’re curious why it’s forbidden, notice that in your case, the Font borrows from ttf_context, which is a local variable. After you move this variable, its address changes, which would make pointers inside Font invalid. Also, nothing in Rust prevents you to change ttf_context without updating writer, which would also be invalid.)

Usual way to solve this problem would be to embed Sld2TtfContext inside the Font, or use Rc (reference-counted pointer), but you can’t change this code, so that option is ruled out.

You can try to use the owning_ref crate or the reffers, but probably that’s not a recommended way.

The simplest way would be to just split your GfxSubsystem into two steps. Unfortunately, that makes the code slightly more complex on the user side.

fn main() {
    let ttf_context = ...;
    let gfx_subsystem = GfxSubsystem::new(&ttf_context);
}

There’s also more user-friendly workaround – check out the leak crate (basicaly, you box the ttf_context and leak it, so it lives until the end of program (that gives you &'static T)).

Of course, you can also use unsafe to convince rust that your reference is gonna be valid, since you store the context in the same struct, but I’d strongly recommend not to use unsafe if you don’t understand thorougly why your code is not accepted as safe. (Also, remember to Box the context first!)

And one more thing about the new signature:

fn new() -> GfxSubsystem<'a> {

Here’s a handly rule: if you see a signature looking like that:

fn new(/* nothing using 'a here (directly or indirectly)*/)
-> SomethingUsing<'a>

it will never work (except from cases, where you eg. return Option<&'a T>, but the actual return value is None).


#7

Thanks krdin.
First the new signature is not necessarily wrong, as I am using the lifetime annotation in the GfxSubsystem struct definition, no?

Anyway, I actually tried your solution to split the GfxSubsystem before you answered (even though I find it surprising I have to modify my design here…). Found a problem with sdl2, see #605. But after the maintainer solved it, I end up with another problem anyway. When calling the load_font method from the ttf_context, the rust compiler is unable to infer an appropriate lifetime for autoref due to conflicting requirements
Apparently, the Font object returned is flagged with the 'static lifetime anotation.


#8

Ok so the library maintainer helped me to figure out my issue. The Font object takes two lifetime annotations, one is supposed to the lifetime of the ttf_context:

extern crate sdl2;

use sdl2::ttf;

static FONT_FILE: &'static str = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf";

struct Writer<'a, 'b> {
  font: ttf::Font<'a, 'b>
}

impl<'a, 'b> Writer<'a, 'b> {
  fn new(font: ttf::Font<'a, 'b>) -> Writer<'a, 'b> {
    Writer {
      font: font,
    }
  }
}

struct GfxSubsystem<'a, 'b> {
  sdl_context: sdl2::Sdl,
  writer: Writer<'a, 'b>,
}

impl<'a, 'b> GfxSubsystem<'a, 'b> {
  fn new(ttf_context: &'a ttf::Sdl2TtfContext) -> GfxSubsystem<'a, 'b> {
    let sdl_context = sdl2::init().unwrap();
    let font = ttf_context.load_font(FONT_FILE, 24).unwrap();
    let writer = Writer::new(font);
    GfxSubsystem {
      sdl_context: sdl_context,
      writer: writer,
    }
  }
}

fn main() {
  let ttf_context = sdl2::ttf::init().unwrap();
  let gss = GfxSubsystem::new(&ttf_context);
}

So as a conclusion, as a rust noob, I am surprised at two things here:

  1. I had to modify my design which, even though it was not not perfect, it seemed reasonable
  2. It is surprising to me that we need to know what lifetime annotations to pass around to library functions and objects. To me it requires knowing the implementation detail of the library.

Thanks all for your help anyway…