Denys Séguret
Denys Séguret

Reputation: 382514

Lifetime constraints on a macro returning a value

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 ?

playground

Upvotes: 3

Views: 323

Answers (1)

lucidbrot
lucidbrot

Reputation: 6261

TL;DR

#[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.

playground


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

I think I got it as good as it gets:

playground

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

Related Questions