[HACK] Temporary lifetime extensions for function calls

Hi,

I have recently fought temporary lifetime extensions again and found a workaround to allow callers to select it on demand. It could be used to make format_args!() support let-bindings with just minor modifications (at least I think so).

In particular, the current rules do not consider function calls for temporary lifetime extensions. See this example:

struct Wrapper<'n> {
    name: &'n String,
}

fn wrap<'n>(name: &'n String) -> Wrapper<'n> {
    Wrapper { name: name }
}

fn main() {
    // EXTENDED: open-coded initializer
    let _w: &Wrapper = &Wrapper { name: &format!("foobar") };
    // NOT-EXTENDED: function initializer
    let _w: &Wrapper = &wrap(&format!("foobar"));
    // EXTENDED: hidden function initializer
    let w: &Wrapper = extend!(wrap, &format!("foobar"));

    println!("Name: {}", w.name);
}

In many situations you stumble across this limitation when trying to construct opaque types. Initializers will require you to create temporary let-bindings.

Fortunately, the Deref trait can be used to hide the function call and thus get temporary lifetime extensions even for function calls. I wrote a small extend! macro that showcases this. In this example, you get the extended lifetime even for an opaque initializer.

You can find the implementation of extend! below. I believe it could be modified to take FnOnce and avoid the Copy requirement, but not without making it significantly more complex.

There are certainly limitations to this. For once, it requires the target type to be a reference (which you could hide in an open-coded new-type). And it requires the function arguments to be given as tuple. And you certainly still have to discuss whether extended lifetimes are desired in your scenario, or whether they confuse your users. Yet, I thought it might be of interest to everyone currently experimenting with lifetime-extensions.

My motiviation was to enable let-bindings for format_args!. I believe this macro could be used as internal implementation detail to make format_args! produce fmt::Arguments { pub inner: &fmt::Internal } in a way that enables let-bindings. Unfortunately, it would be a gross mis-use of Deref, and thus a questionable strategy.

Let me know what you think, or take it and build your own.

--David


A simple implementation of extend! that works with no-std and without unsafe, but has the above mentioned limitations:

pub mod extend {
    pub struct Extend<Call, Arg, Res>
    {
        pub call: Call,
        pub arg: Arg,
        pub value: core::cell::OnceCell<Res>,
    }

    impl<Call, Arg, Res> core::ops::Deref for Extend<Call, Arg, Res>
    where
        Call: Fn(Arg) -> Res,
        Arg: Copy,
    {
        type Target = Res;

        fn deref(&self) -> &Res {
            self.value.get_or_init(|| {
                (self.call)(self.arg)
            })
        }
    }
}

macro_rules! extend {
    ($call: expr, $arg: expr) => {
        &extend::Extend {
            call: $call,
            arg: $arg,
            value: core::cell::OnceCell::new(),
        }
    };
}
4 Likes

I think a better solution for the project would be for temporary lifetime extension to project through the function call, so this "hack" probably isn't something that should make it into the standard library for preservation evermore. But it's an interesting approach nonetheless and might be of some use in a third party utility crate?

1 Like