SDL2 lifetime question

This is my first ever rust program... building a snake game.

Everything was going fantastically, until I tried to add text... I have spent 3.5 hours now trying to debug this lifetime and ... it is going soul crushingly bad.

I did find this, but I couldn't really deduce the solution: Font and Lifetime · Issue #605 · Rust-SDL2/rust-sdl2 · GitHub

I don't think I need to put all my code, but the relevant part is here...

Based on my understanding, it seems Font can outlive Context... but why can I not define these together? I don't want to define the ttf context in main as I was trying to keep it compartmentalized.

pub struct TextContext {
    ttf_context: sdl2::ttf::Sdl2TtfContext,
    pub font: sdl2::ttf::Font<'static, 'static>,
}

impl TextContext {
    pub fn new(path: &str, size: u16) -> Self {
        let ttf_context = sdl2::ttf::init().unwrap();
        let font = ttf_context.load_font(path, size).unwrap();
        Self { ttf_context, font }
    }
}

signature

pub fn load_font<'ttf, P>(&'ttf self, path: P, point_size: u16) -> Result<Font<'ttf, 'static>, String>
where
    P: AsRef<Path>,
...
pub struct Font<'ttf_module, 'rwops> {
    raw: *mut ttf::TTF_Font,
    // RWops is only stored here because it must not outlive
    // the Font struct, and this RWops should not be used by
    // anything else
    // None means that the RWops is handled by SDL itself,
    // and Some(rwops) means that the RWops is handled by the Rust
    // side
    #[allow(dead_code)]
    rwops: Option<RWops<'rwops>>,
    #[allow(dead_code)]
    _marker: PhantomData<&'ttf_module ()>,
}

Here are the errors:

H src/window.rs 42:13 binding `ttf_context` declared here
H src/window.rs 43:20 argument requires that `ttf_context` is borrowed for `'static`
E src/window.rs 43:20 `ttf_context` does not live long enough
H src/window.rs 43:20 borrow of `ttf_context` occurs here
E src/window.rs 44:16 cannot move out of `ttf_context` because it is borrowed
H src/window.rs 45:5  `ttf_context` dropped here while still borrowed

In my main.rs it looks like this:

let mut window = Window::new(WIDTH * SCALE, HEIGHT * SCALE);
let text_context = TextContext::new("src/fonts/daydream.ttf", 18);
...
let text = format!("Score: {}", &game.score);
draw_text(&mut window, &text, &text_context, 10, 10);
...
// inside draw_text()
let surface = text_context.font.render(text).blended(GREEN).unwrap();

damn.............. i just actually figured it out right as I gave up...

pub struct TextContext {
    pub ttf_context: sdl2::ttf::Sdl2TtfContext,
}

impl TextContext {
    pub fn new() -> Self {
        let ttf_context = sdl2::ttf::init().unwrap();
        Self { ttf_context }
    }
}

pub struct Text<'ttf, 'b> {
    pub font: sdl2::ttf::Font<'ttf, 'b>,
}

impl<'ttf, 'b> Text<'ttf, 'b> {
    pub fn new(ttf_context: &'ttf sdl2::ttf::Sdl2TtfContext, path: &str, size: u16) -> Self {
        let font = ttf_context.load_font(path, size).unwrap();
        Self { font }
    }
}

and...

    let text_context = TextContext::new();
    let font = Text::new(&text_context.ttf_context, "src/fonts/daydream.ttf", 18).font;

I would still love an explanation though why these can't live within the same struct. isnt it obvious both live the same lifetime when theyre on the same struct?

I basically split it up, and then passed it in

A borrowed value doesn't just have to continue existing, but to stay at the same address and not be mutated. TextContext doesn't enforce either of those properties for its ttf_context field.

1 Like

I did not do the legwork to understand SDL2.

But if I understand your code correctly, you had a situation like so:

// The fields don't matter for this illustration, just the lifetimes involved
struct Font<'a>(&'a str);
struct Context;

impl Context {
    // Creation of a font borrows the context
    fn load_font(&self) -> Font<'_> {
        Font("dummy")
    }
}

And you wanted to store them together:

struct StoreTogether<'a> {
    ctx: Context,
    // Borrowed from the `ctx` field
    font: Font<'a>,
}

That would be a self-referential struct, which is an anti-pattern in Rust. You can't actually created it without borrowing StoreTogether<'a> for 'a, which makes it mostly unusable (if it can compile at all).


Rust lifetimes (those '_ things) don't describe the length of a value's liveness, such as how long StoreTogether<'_> exists before destructed. When you create a sdl2::ttf::Font<'ttf, '_>, 'ttf represents the length of the borrow of the sdl2::ttf::Sdl2TtfContext. It doesn't represent the Sdl2TtfContext still being alive.

(Lifetimes are a compile-type analysis and don't exist at runtime at all.)

1 Like

How might i achieve this by having them in the same struct? I guess that is the real learning opportunity here, because thats what i tried to do for a few hours

Ah ok, that reply i just made was for the other poster. Let me try to absorb this, thank you!

There's no way to create self-referential structs in safe Rust that results in something useful.[1] Most or all attempts to do so with unsafe are unsound in at least some ways, i.e. it is quite difficult to do correctly.

I think the crates which have come the furthest are ouroboros and yoke. I haven't checked lately to see how many soundness holes they have.


  1. All exceptions I've seen were not used seriously and could have been accomplished in other ways. ↩︎

ah ok.. this is making sense I think. I dont desire to do that, id rather just do it the idiomatic way.

I guess that means mostly what I already did. I'm going to read more about this... thank you!

1 Like

Btw I checked out your site... very cool. I tried out your cube game, sadly all the deps are missing. Looks sweet

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.