More trouble lifetime-land

The crate cosmic_text recommends a single instance of FontSystem and SwashCache<'a> per application.

This is the example they give:

let font_system = FontSystem::new();
let mut cache = SwashCache::new(&font_system);

// Do text rendering

And you can proceed with your text-rendering desires as needed. But since their recommendation to keep these two instances across the application, I'm left with a connundrum. Rust doesn't seem to allow me to do this, because cache holds a reference to font_system. Immutable or not, this now means it cannot move (i.e. placing it into a struct or whatnot)

So I tried pinning it.

let font_system = Box::pin(FontSystem::new());
let mut cache = SwashCache::new(font_system.deref());

// Do text rendering

Which works if I use it like that, but I still can't place it into a struct. Now theoretically, I know why Rust won't allow me to do this, but I don't seem to know how to progress.

My goal is to implement a rich-text editor using cosmic_text, where I'd like to be able to reuse the SwashCache<'a> and FontSystem, for performance and memory consumption reasons.

struct Text<'a> {
  font_system: Pin<Box<FontSystem>>,
  cache: SwashCache<'a>
}

impl<'a> Text<'a> {
  pub fn new() -> Self {
    let font_system = Box::pin(FontSystem::new());
    let cache = SwashCache::new(font_system.deref());
    Self {
      font_system,
      cache
    }
  }
}

struct RichTextEditor<'a> {
  text: &'a mut Text<'a>
}

impl<'a> RichTextEditor<'a> {
  // render text here
}

So to summarise this wall of text, how can I reuse these two values, ideally avoiding unsafe code, keeping the SwashCache<'a> in an Option / lateinit values?

Thank you

Rust doesn't provide any safe way to do that. Here's an unsafe way to do it: playground

Note that it's not sound to provide methods that return mutable references to them. In the case of FontSystem, this is because the cache holds a reference to it. In the case of SwashCache, this is because the caller could mem::swap it.

That said, it would be ok to provide methods like these:

impl FontAndCache {
    pub fn get_image_uncached(&mut self, cache_key: CacheKey) -> Option<SwashImage> {
        let cache = unsafe { &mut *addr_of_mut!((*self.inner).cache) };
        cache.get_image_uncached(cache_key)
    }
}

Safe (or rather safely abstracted, so you don’t need to touch the unsafety yourself) solutions are probably possible via crates such as ouroboros - Rust.

1 Like

Thanks @alice I feared Rust might not allow that, but thanks for the workaround. I'll keep you posted

This looks promising too... I wonder how I never knew about this

From doing soundness reviews on ouroboros (and some other comparable crates), I know there’s an insane amount of subtle ways to get such “self-referencing” data structures wrong. While some of the more subtle points perhaps/maybe only apply to soundness of generalist crates that try to offer support for all possible types, where every component could have different variance, possibly custom drop implementations, there could be chains of borrowing, etc, etc…, the fact that I cannot keep the list of “all surprising issues that came up over multiple occasions of on and off reviewing (on different self-referencing-structs crates)” in my head in the first place means that I personally would generally avoid to hand-write my own self-referencing data structure, unless I have a good reason to do so.

To name another crate I’ve done some review on and that might work for you as well, self_cell - Rust is a more minimalistic crate here which does not need any procedural macros parsing facilities and does not support anything fancy beyond simply two fields, one immutably borrowing the other.

3 Likes

It does seem somewhat like black magic to be able to just install a crate and suddenly it just works, so thank you for assuring me my gut feeling is right :stuck_out_tongue:

Using self-referential types is usually a bad idea, even if you use popular crates to provide the unsafe bits. I would suggest creating the required structures somewhere at the top level (possibly even as a static variable), and passing them down to consuming functions. This way you never need to move them.

That was my ambition with the Box::pin - that they don't move, but I guess this somehow isn't reflected in the type system.

Anyways, I did try the following expressions, none of which seemed to satisfy the compiler;

lazy_static! { pub static ref FONT: FontSystem = FontSystem::new(); };
lazy_static! { pub static ref CACHE: SwashCache<'static> = SwashCache::new(&FONT); };
lazy_static! {pub static ref FONT: (FontSystem, SwashCache<'static>) = {
  let font_system = FontSystem::new();
  let cache = SwashCache::new(&font_system);

  (font_system, cache)
};
static FONT: FontSystem = FontSystem::new();
static CACHE: SwashCache<'static> = SwashCache::new(&FONT);

The reason the first fails is because the lazy_static macro doesn't ensure the variable is declared at compile-time, but rather the code necessary to initialise it is, thus the compiler doesn't know the reference to FONT is valid at all.

The reason the second failed is clear to me, it's the same reason all the others fail - because the value of font_system moves, so the reference to it is invalidated.

The third failed because neiterh FontSystem::new() nor SwashCache::new(&FontSystem) are const, allowing them to be initialised in a static or a const

I suggest using once_cell:

use once_cell::sync::OnceCell; // 1.17.0

#[derive(Debug)]
struct Foo();

#[derive(Debug)]
struct Bar<'a>(&'a Foo);

static FOO: OnceCell<Foo> = OnceCell::new();
static BAR: OnceCell<Bar<'static>> = OnceCell::new();

fn f(_: &Foo, _: &Bar<'_>) {}

const INIT_ERR: &str = "wrong initialization order";

fn main () {
    FOO.set(Foo()).expect(INIT_ERR);
    
    let foo: &'static Foo = FOO.get().expect(INIT_ERR);
    BAR.set(Bar(foo)).expect(INIT_ERR);
    
    f(foo, BAR.get().unwrap());
}

Playground

3 Likes

Whoa that's some wizardry right there :laughing:

Gimme a lil' bit, I'll need to experiment with that one

I'm somewhat surprised the first approach with two lazy_statics doesn't work. What's the error message?

Tested it myself. The only error I get is

error: expected item, found `;`
 --> src/main.rs:5:70
  |
5 | lazy_static! { pub static ref FONT: FontSystem = FontSystem::new(); };
  |                                                                      ^ help: remove this semicolon

error: expected item, found `;`
 --> src/main.rs:6:85
  |
6 | lazy_static! { pub static ref CACHE: SwashCache<'static> = SwashCache::new(&FONT); };
  |                                                                                     ^ help: remove this semicolon

After removing the semicolons, the code compiles fine.

Hm, not sure what I missed. I'll try that one again, and let you know

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.