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.
- 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.
- 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
- 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()
}