How to get function's input/output value by procedural macro?

just like:

[#func_log_macro]
fn test(a: i32, b: impl number) -> i64 { 1024i64 }

then i could make a vector to receive the values or just print in terminal.

func_log = FuncLog::new();
test(1i32, 2i64);
test(1i32, 2i32);
assert_eq!(func_log, [(1i32, 2i64, 1024i64), (1i32, 2i32, 1024i64)]);

Rust's macros read in some source text and generate some more source code, so anything a human can do with an editor, a proc-macro can do automatically.

That means your #[func_log_macro] custom attribute will need to generate a function that stashes its arguments "somewhere" before passing them to the wrapped test() function. You'd also need to provide a way for your FuncLog to get at the values.

Maybe you could generate something like this:

[#func_log_macro]
fn test(a: i32, b: impl number) -> i64 { 1024i64 }

// expands to

static CAPTURED_ARGUMENTS: Mutex<Vec<(i32, ...)>> = Mutex::new(Vec::new());

fn test(a: i32, b: impl number) -> i64 {
  // the original function
  fn test(a: i32, b: impl number) -> i64 { 1024i64 }

  CAPTURED_ARGUMENTS.lock().unwrap().push((a, b));

  test(a, b)
}

struct FuncLog;

impl FuncLog {
  fn get(&self) -> impl Deref<Target=[(i32, ...)]> { 
    CAPTURED_ARGUMENTS.lock().unwrap()
  }
}

Regardless of the approach, you are probably going to run into a couple problems that you normally wouldn't see in a garbage collected language like JavaScript, Java, or Python.

  1. Moves - in general, if you stash away your function's arguments, you can't also pass them to test() by value because they would have been moved. This could be "fixed" by making a copy of the value with clone(), but that has non-trivial performance implications.
  2. Generics - if I don't know the concrete type for b: impl number, then how can I store it in a static variable? You'll see I used ... in the above snippet because I didn't know what to write. If test() is first called where b is a f32, then the next call passes in b: u64, the tuple (i32, f32) and (i32, u64) (our arguments) can't be put in the same Vec because a Vec requires each of its elements to have the exact same type because of how things are laid out in memory
  3. Ownership & synchronisation - Does the FuncLog own any values that were captured during any test() calls between the time it was created and the time it goes out of scope? What if I create multiple FuncLog objects in my main() function that are alive at the same time? And how does all of this fit into threading?

Thanks for reply. generics is a complex problem, just ignore it. i just wrote a macro, but don't know how to got the input/output.

pub(crate) fn running_info(_attr: TokenStream, func: TokenStream) -> TokenStream {
    let func = parse_macro_input!(func as ItemFn);
    let func_vis = &func.vis; // like pub
    let func_block = &func.block; // { some statement or expression here }

    let func_decl = func.sig;
    let func_name = &func_decl.ident; // function name
    let func_name_string = func_name.to_string();
    let func_generics = &func_decl.generics;
    let func_inputs = &func_decl.inputs;
    let func_output = &func_decl.output;

    let caller = quote! {
        #func_vis fn #func_name #func_generics(#func_inputs) #func_output {
            use std::time;

            let start = time::Instant::now();
            #func_block
            println!("{}({}) -> {}: {:?}", #func_name_string, ????args, ????result, start.elapsed());
        }
    };

    caller.into()
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.