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(),
}
};
}