Ayu*_*hra 6 rust rust-macros rust-proc-macros
我需要获取每个方法的调用者的源位置。我正在尝试创建一个proc_macro_attribute来捕获位置并打印它。
#[proc_macro_attribute]
pub fn get_location(attr: TokenStream, item: TokenStream) -> TokenStream {
// Get and print file!(), line!() of source
// Should print line no. 11
item
}
Run Code Online (Sandbox Code Playgroud)
#[get_location]
fn add(x: u32, y: u32) -> u32 {
x + y
}
fn main() {
add(1, 5); // Line No. 11
}
Run Code Online (Sandbox Code Playgroud)
长话短说
\n\n\n\n// print_caller_location/src/lib.rs\n\nuse proc_macro::TokenStream;\nuse quote::quote;\nuse syn::spanned::Spanned;\n\n// Create a procedural attribute macro\n//\n// Notably, this must be placed alone in its own crate\n#[proc_macro_attribute]\npub fn print_caller_location(_attr: TokenStream, item: TokenStream) -> TokenStream {\n // Parse the passed item as a function\n let func = syn::parse_macro_input!(item as syn::ItemFn);\n\n // Break the function down into its parts\n let syn::ItemFn {\n attrs,\n vis,\n sig,\n block,\n } = func;\n\n // Ensure that it isn\'t an `async fn`\n if let Some(async_token) = sig.asyncness {\n // Error out if so\n let error = syn::Error::new(\n async_token.span(),\n "async functions do not support caller tracking functionality\n help: consider returning `impl Future` instead",\n );\n\n return TokenStream::from(error.to_compile_error());\n }\n\n // Wrap body in a closure only if function doesn\'t already have #[track_caller]\n let block = if attrs.iter().any(|attr| attr.path.is_ident("track_caller")) {\n quote! { #block }\n } else {\n quote! {\n (move || #block)()\n }\n };\n\n // Extract function name for prettier output\n let name = format!("{}", sig.ident);\n\n // Generate the output, adding `#[track_caller]` as well as a `println!`\n let output = quote! {\n #[track_caller]\n #(#attrs)*\n #vis #sig {\n println!(\n "entering `fn {}`: called from `{}`",\n #name,\n ::core::panic::Location::caller()\n );\n #block\n }\n };\n\n // Convert the output from a `proc_macro2::TokenStream` to a `proc_macro::TokenStream`\n TokenStream::from(output)\n}\nRun Code Online (Sandbox Code Playgroud)\n\n确保将其放入板条箱中并将这些行添加到其Cargo.toml:
# print_caller_location/Cargo.toml\n\n[lib]\nproc-macro = true\n\n[dependencies]\nsyn = {version = "1.0.16", features = ["full"]}\nquote = "1.0.3"\nproc-macro2 = "1.0.9"\nRun Code Online (Sandbox Code Playgroud)\n\n宏只能扩展到可以手动编写的代码。知道了这一点,我在这里看到两个问题:
\n\n\n简短的回答:要获取调用函数的位置,请将其标记为
\n#[track_caller]并使用std::panic::Location::caller并在其主体中
我们想要一个程序宏
\n\n#[track_caller],Location::caller.例如,它将转换这样的函数:
\n\nfn foo() {\n // body of foo\n}\nRun Code Online (Sandbox Code Playgroud)\n\n进入
\n\n#[track_caller]\nfn foo() {\n println!("{}", std::panic::Location::caller());\n // body of foo\n}\nRun Code Online (Sandbox Code Playgroud)\n\n下面,我提出了一个程序宏,它精确地执行该转换 \xe2\x80\x94 尽管,正如您将在以后的版本中看到的那样,您可能想要不同的东西。要尝试此代码,就像之前在 TL;DR 部分中一样,将其放入自己的 crate 中并将其依赖项添加到Cargo.toml.
// print_caller_location/src/lib.rs\n\nuse proc_macro::TokenStream;\nuse quote::quote;\n\n// Create a procedural attribute macro\n//\n// Notably, this must be placed alone in its own crate\n#[proc_macro_attribute]\npub fn print_caller_location(_attr: TokenStream, item: TokenStream) -> TokenStream {\n // Parse the passed item as a function\n let func = syn::parse_macro_input!(item as syn::ItemFn);\n\n // Break the function down into its parts\n let syn::ItemFn {\n attrs,\n vis,\n sig,\n block,\n } = func;\n\n // Extract function name for prettier output\n let name = format!("{}", sig.ident);\n\n // Generate the output, adding `#[track_caller]` as well as a `println!`\n let output = quote! {\n #[track_caller]\n #(#attrs)*\n #vis #sig {\n println!(\n "entering `fn {}`: called from `{}`",\n #name,\n ::core::panic::Location::caller()\n );\n #block\n }\n };\n\n // Convert the output from a `proc_macro2::TokenStream` to a `proc_macro::TokenStream`\n TokenStream::from(output)\n}\nRun Code Online (Sandbox Code Playgroud)\n\n用法示例:
\n\n// example1/src/main.rs\n\n#![feature(track_caller)]\n\n#[print_caller_location::print_caller_location]\nfn add(x: u32, y: u32) -> u32 {\n x + y\n}\n\nfn main() {\n add(1, 5); // entering `fn add`: called from `example1/src/main.rs:11:5`\n add(1, 5); // entering `fn add`: called from `example1/src/main.rs:12:5`\n}\nRun Code Online (Sandbox Code Playgroud)\n\n不幸的是,我们无法摆脱这个简单的版本。该版本至少存在两个问题:
\n\n它如何与 s 组成async fn:
#[print_caller_location]。例如:// example2/src/main.rs\n\n#![feature(track_caller)]\n\n#[print_caller_location::print_caller_location]\nasync fn foo() {}\n\nfn main() {\n let future = foo();\n // ^ oops! prints nothing\n futures::executor::block_on(future);\n // ^ oops! prints "entering `fn foo`: called from `example2/src/main.rs:5:1`"\n let future = foo();\n // ^ oops! prints nothing\n futures::executor::block_on(future);\n // ^ oops! prints "entering `fn foo`: called from `example2/src/main.rs:5:1`"\n}\nRun Code Online (Sandbox Code Playgroud)它如何与自身的其他调用一起工作,或者一般来说#[track_caller]:
#[print_caller_location]将打印根调用者的位置,而不是给定函数的直接调用者。例如: // example3/src/main.rs\n\n#![feature(track_caller)]\n\n#[print_caller_location::print_caller_location]\nfn add(x: u32, y: u32) -> u32 {\n x + y\n}\n\n#[print_caller_location::print_caller_location]\nfn add_outer(x: u32, y: u32) -> u32 {\n add(x, y)\n // ^ we would expect "entering `fn add`: called from `example3/src/main.rs:12:5`"\n}\n\nfn main() {\n add(1, 5);\n // ^ "entering `fn add`: called from `example3/src/main.rs:17:5`"\n add(1, 5);\n // ^ "entering `fn add`: called from `example3/src/main.rs:19:5`"\n add_outer(1, 5);\n // ^ "entering `fn add_outer`: called from `example3/src/main.rs:21:5`"\n // ^ oops! "entering `fn add`: called from `example3/src/main.rs:21:5`"\n //\n // In reality, `add` was called on line 12, from within the body of `add_outer`\n add_outer(1, 5);\n // ^ "entering `fn add_outer`: called from `example3/src/main.rs:26:5`"\n // oops! ^ entering `fn add`: called from `example3/src/main.rs:26:5`\n //\n // In reality, `add` was called on line 12, from within the body of `add_outer`\n}\nRun Code Online (Sandbox Code Playgroud)async fn可以async fn使用 s来解决这个问题-> impl Future,例如,如果我们希望async fn反例正常工作,我们可以写:
// example2/src/main.rs\n\n#![feature(track_caller)]\n\n#[print_caller_location::print_caller_location]\nasync fn foo() {}\n\nfn main() {\n let future = foo();\n // ^ oops! prints nothing\n futures::executor::block_on(future);\n // ^ oops! prints "entering `fn foo`: called from `example2/src/main.rs:5:1`"\n let future = foo();\n // ^ oops! prints nothing\n futures::executor::block_on(future);\n // ^ oops! prints "entering `fn foo`: called from `example2/src/main.rs:5:1`"\n}\nRun Code Online (Sandbox Code Playgroud)\n\n我们可以添加一个特殊情况,将此转换应用于我们的宏。async fn foo()然而,除了fn foo() -> impl Future<Output = ()>影响返回的 future 可能具有的 auto 特征之外,该转换还将函数的公共 API 从 更改为。
因此,我建议我们允许用户根据需要使用该解决方法,并且如果我们的宏在async fn. 我们可以通过将这些行添加到我们的宏代码中来做到这一点:
// example3/src/main.rs\n\n#![feature(track_caller)]\n\n#[print_caller_location::print_caller_location]\nfn add(x: u32, y: u32) -> u32 {\n x + y\n}\n\n#[print_caller_location::print_caller_location]\nfn add_outer(x: u32, y: u32) -> u32 {\n add(x, y)\n // ^ we would expect "entering `fn add`: called from `example3/src/main.rs:12:5`"\n}\n\nfn main() {\n add(1, 5);\n // ^ "entering `fn add`: called from `example3/src/main.rs:17:5`"\n add(1, 5);\n // ^ "entering `fn add`: called from `example3/src/main.rs:19:5`"\n add_outer(1, 5);\n // ^ "entering `fn add_outer`: called from `example3/src/main.rs:21:5`"\n // ^ oops! "entering `fn add`: called from `example3/src/main.rs:21:5`"\n //\n // In reality, `add` was called on line 12, from within the body of `add_outer`\n add_outer(1, 5);\n // ^ "entering `fn add_outer`: called from `example3/src/main.rs:26:5`"\n // oops! ^ entering `fn add`: called from `example3/src/main.rs:26:5`\n //\n // In reality, `add` was called on line 12, from within the body of `add_outer`\n}\nRun Code Online (Sandbox Code Playgroud)\n\n#[print_caller_location]函数的嵌套行为有问题的行为可以最小化为这样一个事实:当一个#[track_caller]函数foo直接调用另一个#[track_caller]函数时bar,Location::caller它们都可以访问foo\ 的调用者。换句话说,Location::caller在嵌套函数的情况下,可以访问根调用者#[track_caller]:
// example4/src/main.rs\n\n#![feature(track_caller)]\n\nuse std::future::Future;\n\n#[print_caller_location::print_caller_location]\nfn foo() -> impl Future<Output = ()> {\n async move {\n // body of foo\n }\n}\n\nfn main() {\n let future = foo();\n // ^ prints "entering `fn foo`: called from `example4/src/main.rs:15:18`"\n futures::executor::block_on(future);\n // ^ prints nothing\n let future = foo();\n // ^ prints "entering `fn foo`: called from `example4/src/main.rs:19:18`"\n futures::executor::block_on(future);\n // ^ prints nothing\n}\nRun Code Online (Sandbox Code Playgroud)\n\n\n\n为了解决这个问题,我们需要打破调用链#[track_caller]。bar我们可以通过隐藏闭包内的嵌套调用来打破链条:
// Ensure that it isn\'t an `async fn`\nif let Some(async_token) = sig.asyncness {\n // Error out if so\n let error = syn::Error::new(\n async_token.span(),\n "async functions do not support caller tracking functionality\n help: consider returning `impl Future` instead",\n );\n\n return TokenStream::from(error.to_compile_error());\n}\nRun Code Online (Sandbox Code Playgroud)\n\n\n\n现在我们知道了如何打破函数链#[track_caller],我们就可以解决这个问题了。我们只需要确保,如果用户确实#[track_caller]有意标记他们的函数,我们就不会插入闭包并破坏链条。
我们可以将这些行添加到我们的解决方案中:
\n\n#![feature(track_caller)]\n\nfn main() {\n foo(); // prints `src/main.rs:4:5` instead of the line number in `foo`\n}\n\n#[track_caller]\nfn foo() {\n bar();\n}\n\n#[track_caller]\nfn bar() {\n println!("{}", std::panic::Location::caller());\n}\nRun Code Online (Sandbox Code Playgroud)\n\n经过这两项更改后,我们最终得到了以下代码:
\n\n#![feature(track_caller)]\n\nfn main() {\n foo();\n}\n\n#[track_caller]\nfn foo() {\n (move || {\n bar(); // prints `src/main.rs:10:9`\n })()\n}\n\n#[track_caller]\nfn bar() {\n println!("{}", std::panic::Location::caller());\n}\nRun Code Online (Sandbox Code Playgroud)\n
可以使用现成的解决方案(请参阅@timotree 的评论)。如果您想自己执行此操作,具有更大的灵活性或学习,您可以编写一个过程宏来解析回溯(从调用的函数内部获取)并打印您需要的信息。这是 a 中的程序宏lib.rs:
extern crate proc_macro;
use proc_macro::{TokenStream, TokenTree};
#[proc_macro_attribute]
pub fn get_location(_attr: TokenStream, item: TokenStream) -> TokenStream {
// prefix code to be added to the function's body
let mut prefix: TokenStream = "
// find earliest symbol in source file using backtrace
let ps = Backtrace::new().frames().iter()
.flat_map(BacktraceFrame::symbols)
.skip_while(|s| s.filename()
.map(|p|!p.ends_with(file!())).unwrap_or(true))
.nth(1 as usize).unwrap();
println!(\"Called from {:?} at line {:?}\",
ps.filename().unwrap(), ps.lineno().unwrap());
".parse().unwrap(); // parse string into TokenStream
item.into_iter().map(|tt| { // edit input TokenStream
match tt {
TokenTree::Group(ref g) // match the function's body
if g.delimiter() == proc_macro::Delimiter::Brace => {
prefix.extend(g.stream()); // add parsed string
TokenTree::Group(proc_macro::Group::new(
proc_macro::Delimiter::Brace, prefix.clone()))
},
other => other, // else just forward TokenTree
}
}).collect()
}
Run Code Online (Sandbox Code Playgroud)
解析回溯以查找源文件中最早的符号(使用file!()另一个宏检索)。我们需要添加到函数中的代码在字符串中定义,然后将其解析为 aTokenStream并添加到函数体的开头。我们可以在最后添加这个逻辑,但是返回一个没有分号的值就不再起作用了。然后,您可以在您的程序中使用程序宏,main.rs如下所示:
extern crate backtrace;
use backtrace::{Backtrace, BacktraceFrame};
use mylib::get_location;
#[get_location]
fn add(x: u32, y: u32) -> u32 { x + y }
fn main() {
add(1, 41);
add(41, 1);
}
Run Code Online (Sandbox Code Playgroud)
输出是:
> Called from "src/main.rs" at line 10
> Called from "src/main.rs" at line 11
Run Code Online (Sandbox Code Playgroud)
不要忘记lib通过将这两行添加到您的Cargo.toml:
[lib]
proc-macro = true
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2589 次 |
| 最近记录: |