Cast from 'a type to 'static type, enforcing the original lifetime at runtime

I need to use a function that accepts a generic argument T: SomeTrait + 'static.
But the argument I need to pass is not 'static, but has a lifetime 'a.
An example of code I need to complement:


trait SomeTrait {
    fn do_something(&self);
}

struct MyType<'a> {
    // ... something that uses 'a
}

impl<'a> SomeTrait for MyType<'a> {
    fn do_something(&self) {
        // ...
    }
}

fn my_code(v: MyType<'a>) {
    dependency(v);
}

fn dependency<T: SomeTrait + 'static>(v: T) {}

Clearly it is not possible to use this code as is, because MyType is not 'static. What i want to do instead is to enforce the lifetime of MyType at runtime: before 'a expires, I want to ensure that the reference that dependency function receives gets destroyed.

So, a rough sketch would be:


impl<'a> SomeTrait for Arc<Mutex<Option<MyType<'a>>>> {
    fn do_something(&self) {
        if self.lock().is_none() {
            return; // do nothing if the reference is destoyed
        }
        // ...
    }
}

fn my_code<'a>(v: MyType<'a>) {
    let my_type_rc = Arc::new(Mutex::new(Some(v)));

    dependency(my_type_rc.clone());
    
    // destroy the reference, to ensure that it cannot be accessed after the lifetime expires
    drop(my_type_rc.lock().take());
}

The code, of course, still doesn't work, since SomeTrait is still implemented for a type that is not 'static - but now it may be casted for 'static, and at runtime I ensure that the reference won't outlive the original lifetime 'a.

Is it possible to do in safe rust? If not, are there some crates that provide safe abstractions for safe temporary convertion from 'a types to 'static types?

Thanks.

Generally this is not possible in safe Rust, and very often needing such cast is a red flag. It may be actually unsafe and cause real crashes and vulnerabilities if you force it through with unsafe code.

In many places in Rust 'static actually means that temporary references are completely forbidden, so data structures containing temporary references are not supposed to work, and forcing it through can break real safety protections.

'static bound is sometimes unintentionally implied when using Box<dyn T>. In that case you should make all the code allow temporary references, instead of making everything else use 'static (like leaked memory).

Forcing through 'static used to be needed for scoped threads, but now std has real scoped threads you can use, or use rayon with scopes or join.

Most likely, you need to stop putting & temporary references in structs, and instead use Box or Arc to store data "by reference".

Note that it's always impossible to make references live longer. They can only be shortened, and they "infect" everything making their lifetime shorter and temporary. Putting MyType<'temp> in Arc doesn't make it live as long as Arc. Instead, it makes Arc as restricted and temporary as 'temp. Everything with lifetimes always gets the worst case.

2 Likes

There is not and cannot be a safe abstraction for doing so. There are very specific cases where it is sound to do so, but it is impossible for a generic library to know whether those cases apply, so such a library would necessarily be unsound.

In order to give you any suggestions for how to proceed with your actual problem, we will need to know the concrete situation: exactly what T: 'static bound is giving you a problem? No // ... generalities: give us the docs.rs link for the exact function or type involved, or your own code that has that bound, and what it is that you need to pass in instead.

3 Likes

In my specific case I need to expose some egui UI api to lua scripts using mlua.

The main type for drawing widgets in egui is egui::Ui, but egui only provides a reference to egui::Ui object and never an owned type:

egui::Window::new("Window").show(ctx, |ui: &mut egui::Ui| ui.label("Hello, world!"));

It provides an owned egui::Context, but it's not used for drawing window contents.
So, I cannot use Arc<egui::Ui> or Box<egui::Ui> because the library doesn't provide an owned egui::Ui. It also wouldn't make much sennse to provide an owned egui::Ui object: it certainly must reference some temporary objects that are used to draw the current frame, and how would it behave outside the content drawing function or even UI thread?

Links: egui::window::show function

Second, I use mlua to support lua scripting, and I want to have API something like this:

egui.window('Window', function(ui)
    ui.label('Hello, world!')
end)

Here ui.label should be a lua wrapper for egui::Ui::label function.

To expose such a ui lua object with label method, lua's userdata can be used. Maybe there are other ways, but I think they'll eventually hit the same limitation userdata does.
Mlua exposes create_userdata function that corresponds to the dependency function in my first post. It requires userdata type to be T: UserData + MaybeSend + 'static.

Clearly, egui::Ui object's live is much shorter than userdata's: egui::Ui lives for just one frame, but userdata doesn't have a limit on its lifetime, so mlua reasonably puts a 'static requirement for the userdata type.
This is where I the problem lies.

So I wanted to create such a userdata that would get the egui::Ui reference just for the duration of the window callback call, and when the callback returns I wanted the reference to be destoyed, while the userdata would continue to live (there is no way to destroy it as well, in particular, because lua script may store a reference to it).

So my draw function would have to look something like this:

struct UiUserdata<'a> {
    ui: &mut egui::Ui,
}

impl<'a> mlua::UserData for UiUserdata<'a> {
    // ...
}

fn draw(ctx: &egui::Context, title: &str, window_callback: mlua::Function) {
    egui::Window::new(title).show(ctx, |ui: &mut egui::Ui| {
        let ui_userdata = lua.create_userdata(UiUserdata {
            ui
        });
        window_callback.call::<()>(ui_userdata);
        // here the reference UiUserdata::ui must be destroyed
    });
}

I see.

In this situation, you don’t actually need to coerce the lifetime to 'static — instead, you can transform the &'a mut Ui into a *mut Ui and store that in an appropriate wrapper type. The wrapper can then be asked to grant a shorter-lived &mut Ui as needed, with runtime checks as you say.

Unfortunately, I don’t know of a library that offers this functionality, but it should be possible to do.

1 Like

Indeed, I think this is a much simpler and cleaner approach! Thank you!

On embedded (Embassy Framework) side, this is used alot.

See crates.io: Rust Package Registry.

You don't need unsafe for this specific case, mlua has a Lua::scope method to temporarily insert non-'static userdata and ensure it is no longer available once the method returns.

3 Likes

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.