Extend lifetime of temporaries
dtolnay opened this issue · 5 comments
The behavior of maplit's macros when temporaries are involved is inconsistent with vec!
and inconsistent with how temporary lifetimes usually work. Temporaries are supposed to outlive the current statement i.e. live "until the semicolon".
use maplit::hashset;
use std::collections::HashSet;
use std::path::Path;
fn v(_: Vec<&str>) {}
fn s(_: HashSet<&str>) {}
fn main() {
let path = Path::new("/");
// fine
v(vec![path.to_string_lossy().as_ref()]);
// does not compile
s(hashset![path.to_string_lossy().as_ref()]);
}
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:15:16
|
15 | s(hashset![path.to_string_lossy().as_ref()]);
| ---------^^^^^^^^^^^^^^^^^^^^^^----------
| | |
| | creates a temporary which is freed while still in use
| temporary value is freed at the end of this statement
| borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
As a proof of concept, an expansion like this would behave correctly with lifetimes of temporaries:
s({
match (path.to_string_lossy().as_ref(), /* ... */) {
(x0, /* ... */) => {
let mut set = HashSet::new();
set.insert(x0);
/* ... */
set
}
}
});
Interesting. Do you rank this as a curiosity, feature request, or bug?
That expansion is interesting but I don't see how we achieve it (need identifiers for the patterns - is there a recursive macro rule-trick for that, that will work here)?
75% feature request + 25% bug. I think this is worth fixing if someone (or I) were to send a PR that addresses this without making the implementation or the generated code inscrutable.
Sounds good. We will also check that compile time for large maps remains in the same ballpark.
What comes to mind is to make it a "builder expression", chained methods, that way the macro can result in mostly a single expression.
The half baked poc is like this
trait SetInsert<K> {
fn maplit_insert(self, k: K) -> Self;
}
impl<K> SetInsert<K> for HashSet<K> where K: Hash + Eq {
fn maplit_insert(mut self, k: K) -> Self { self.insert(k); self }
}
macro_rules! hashset {
(@single $($x:tt)*) => (());
(@count $($rest:expr),*) => (<[()]>::len(&[$(hashset!(@single $rest)),*]));
($($key:expr,)+) => { hashset!($($key),+) };
($($key:expr),*) => {
{
let _cap = hashset!(@count $($key),*);
::std::collections::HashSet::with_capacity(_cap)
$(
.maplit_insert($key)
)*
}
};
}
Would something like
macro_rules! hashset {
(@single $($x:tt)*) => (());
(@count $($rest:expr),*) => (<[()]>::len(&[$(hashset!(@single $rest)),*]));
(@r [$($ks:expr,)*] $k:expr, $($xs:tt)*) => {
{
match $k {
k => hashset!(@r [$($ks,)* k,] $($xs)*)
}
}
};
(@r [$($key:expr,)*]) => {
{
let _cap = hashset!(@count $($key),*);
let mut _set = ::std::collections::HashSet::with_capacity(_cap);
$(
let _ = _set.insert($key);
)*
_set
}
};
($($key:expr,)+) => { hashset!($($key),+) };
($($key:expr),*) => { hashset!(@r [] $($key,)*) };
}
SeeSpring@d842582
work? I'm not sure how much this would affect compile times though.