Reputation: 382514
In the following code the fun
function and the mac
macro do the same thing:
struct S<'s> {
src: &'s str,
off: usize,
}
impl std::fmt::Debug for S<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "* {}", &self.src[self.off..])
}
}
fn fun<'s>(value: &'s str) -> S<'s> {
let s = S{ src: value, off: 1 };
s
}
/// The real macro is variadic, that's why it can't be replaced with a function
#[macro_export]
macro_rules! mac {
($value: expr) => {{
let s = S{ src: $value, off: 1 };
// there should be more here
s
}};
}
pub fn main() {
let p = std::path::PathBuf::from("hi");
// works OK
dbg!(fun(&p.to_string_lossy()));
// works ok
let temp = p.to_string_lossy();
dbg!(mac!(&temp));
// can't be compiled
dbg!(mac!(&p.to_string_lossy()));
}
But the macro can't be called as easily. The last line of code can't be compiled.
How could I fix the macro to avoid the "temporary which is freed while still in use" problem, so that macro users don't have to create uselessly verbose temporary variables ?
Upvotes: 3
Views: 323
Reputation: 6261
#[macro_export]
macro_rules! mag {
($value: expr) => {
($value, S{ src: &$value, off: 1 })
};
}
pub fn main() {
let p = std::path::PathBuf::from("hi");
dbg!(mag!(p.to_string_lossy()).0);
}
I have found a solution, inspired by this Answer:
struct S<'s> {
src: &'s str,
off: usize,
}
impl std::fmt::Debug for S<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "* {}", &self.src[self.off..])
}
}
fn fun<'s>(value: &'s str) -> S<'s> {
let s = S{ src: value, off: 1 };
s
}
/// The real macro is variadic, that's why it can't be replaced with a function
#[macro_export]
macro_rules! mac {
($value: expr) => {{
let s = S{ src: $value, off: 1 };
// there should be more here
s
}};
}
#[macro_export]
macro_rules! macc {
($value: expr, $var0:ident, $var1:ident) => {
let $var1 = $value;
let $var0 = S{ src: &$var1, off: 1 };
// there should be more here
};
}
pub fn main() {
let p = std::path::PathBuf::from("hi");
// works OK
dbg!(fun(&p.to_string_lossy()));
// works ok
let temp = p.to_string_lossy();
dbg!(mac!(&temp));
// CAN be compiled
macc!(p.to_string_lossy(), alpha, beta);
dbg!(alpha);
}
I define a macro macc
instead of your mac
. It takes some identifiers that then become available in your calling scope (i.e. within main()
).
That way, the macro can let beta
with it staying in scope. This is so that the macro does not create unexpected identifiers, overwriting existing variable names.
If the macro is defined within the same function, you can get rid of one of the two identifier arguments:
pub fn main() {
#[macro_export]
macro_rules! maccc {
($value: expr, $var0:ident) => {
let my_own_thing = $value;
let $var0 = S{ src: &my_own_thing, off: 1 };
// there should be more here
};
}
let p = std::path::PathBuf::from("hi");
// works when maccc is defined within the function
maccc!(p.to_string_lossy(), gamma);
dbg!(gamma);
}
struct S<'s> {
src: &'s str,
off: usize,
}
impl std::fmt::Debug for S<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "* {}", &self.src[self.off..])
}
}
#[macro_export]
macro_rules! mag {
($value: expr) => {
($value, S{ src: &$value, off: 1 })
};
}
pub fn main() {
let p = std::path::PathBuf::from("hi");
// works
let (_,m) = mag!(p.to_string_lossy());
dbg!(m);
}
This returns a tuple with the temporary value and s
, so that the temporary value does not yet go out of scope. You can even do it this way:
// works
let m = mag!(p.to_string_lossy()).0;
dbg!(m);
Upvotes: 2