How do you look up a macro in Rust?
E.g., I was reading the source code for serde_json and encountered the macro tri!.
I ended up downloading the code and running spinning up VS Code. But this isn't always feasible. And sometimes it's a different version, etc. I should be able to read code without running it.
But this is only a specific example. How do you look up any macro in Rust, in any codebase"?
Macros have different lookup rules than normal items. You can find details in the Scoping, Exporting, and Importing section for Macros by Example in the Rust Reference.
Basically there are two kinds of scopes to lookup macros:
path based scoping: which is the same as normal item lookup where it can be imported by use statement or by path:
use macros::a;
a!();
macros::b!();
textual scoping: a non-path-qualified macro will first look at macro definitions in the current module and all parent modules. I think this is better shown with an example:
mod a {
macro_rules! macro_a { () => {}; }
mod b {
macro_rules! macro_b { () => {}; }
mod c {
macro_rules! macro_c { () => {}; }
macro_a!(); // these all work
macro_b!(); // without any
macro_c!(); // explicit imports
}
}
}
This will work across files as well. However, keep in mind that macro definitions are ordered, meaning a macro defined after its use is not considered.
So when looking at source code, I generally search for macros in this order:
Look in the current module for any macro_rules! definitions or matching import statements. This can get hairy if there are wild-card imports, but that's just how it goes sometimes; use your best judgement if the macro could be there.
Look in the crate root (usually main.rs or lib.rs) for similar definitions or imports. This is because often crate-internal macros are designed to be used crate-wide, and following textual lookup above, any macro in the crate root is available everywhere. This is also the likely place where you'll find #[macro_use] definitions either for some internal macros module (as is the case with serde-json), or on extern crate ...; declarations (where they must be at the crate root).
Then look in intermediate modules. I don't think I've ever had to do this personally, but its possible.
Related
I'm writing code to parse HTTP requests. I'd rather keep some things like request methods and response codes as enums because of their exhaustibility.
But it also means that for each of those enums I would have to make a From<String> implementation. The obvious way to do it is to match against a plethora of constant strings. That seems like a lot of almost identical code which I would want to factor out.
Someone suggested I use a macro to do it, but my knowledge of rust macros is limited so it made me question some things:
How can I ensure the argument is of type &str or String? Macros can take expressions, but I don't want to use type_name and panic if it doesn't match as it seems to be a very dirty fix.
How can I convert a string to a token stream? So far I've seen that the proc_macro crate offers that functionality, but is it possible to do with simple declarative macro?
Is it possible to achieve one to one mapping by any other means? I hoped I could pre-initialize enum variants with some known values like in Java.
You really want to use a library for this. You can write the macro yourself, but someone else has already made a battle-tested version of what you're trying to do.
Use strum's EnumString derive:
In Cargo.toml:
[dependencies]
strum = "0.24"
strum_macros = "0.24"
In your source code: (eg main.rs)
use std::str::FromStr;
use strum_macros::EnumString;
#[derive(Debug, EnumString)]
enum MyEnum {
Foo,
Bar,
Baz,
}
fn main() {
dbg!(MyEnum::from_str("Foo").unwrap());
}
Gives you
[src/main.rs:12] MyEnum::from_str("Foo").unwrap() = Foo
For more details see The documentation for the FromString derive and strum's readme.
I am creating an attribute macro that can be invoked with (a path to) some type amongst its attributes:
#[my_macro(path::to::SomeType)]
This macro generates a module, within which the specified type is to be used:
mod generated {
use path::to::SomeType;
}
However, from within that generated module, the path as provided may no longer be correct:
if the path is prefixed with ::, it remains correct—I can determine this within the proc macro;
otherwise, if the path starts with an identifier that is in a prelude (such as that of an external crate), it remains correct—I can't see any way to determine this within the proc macro;
otherwise it must now be prefixed with super:: for resolution to succeed.
Of course, I could require users of the macro to provide a path that will be correct from within the generated module, but that would not be very ergonomic.
Instead, I'm currently generating a type alias within the parent module:
type __generated_SomeType = path::to::SomeType;
mod generated {
use super::__generated_SomeType as SomeType;
}
However this is not only a tad clunky, but it furthermore pollutes the parent namespace.
Is there a better way?
I don't think there's a satisfying solution. But there's an alternative, and a way to improve your current approach.
The alternative is to use a const item:
const _: () = {
// ... your generated code here
};
It can still cause name collisions with items from the parent scope, but at least the generated items aren't visible outside of the const. This is what most macro authors do.
If you don't want to do that, you can go with your current approach, but you should add #[doc(hidden)] to the __generated_SomeType type, so it doesn't show up in documentation. Also, you should use an import instead of a type alias to support generic types, as suggested by #ChayimFriedman:
#[doc(hidden)]
use path::to::SomeType as __generated_SomeType;
I was just about to suggest combining these to approaches by putting a module inside a const item, but it doesn't work:
const _: () = {
use path::to::SomeType as __generated_SomeType;
mod generated {
// doesn't work :(
use super::__generated_SomeType as SomeType;
}
};
Rust complains that __generated_SomeType doesn't exist in the parent module.
One of my dependencies has a few println! scattered around that seem to be debugging artifacts and I'm trying to find a way to suppress them, but haven't had any success so far.
I've been looking at macros, and tried putting one inside the function where the imported struct is instantiated but with no success, likewise when giving it a global scope.
macro_rules! println {
($($rest:tt)*) => { }
}
Is there any way I can use a macro to target an external crate, or any other solution short of forking the codebase?
If I try to test the final code (including hidden code which wraps the whole file in a main function) of chapter 2 of Learning Rust With Entirely Too Many Lists I get this error:
error[E0432]: unresolved import `super::List` --> src/second.rs:110:9
| 110 | use super::List;
| ^^^^^^^^^^^ no `List` in `second`
Turns out if I remove the hidden code that wraps a main function around the displayed code I can run tests just fine. This does raise the question of what the purpose was of this hidden code that broke the use statement. Also no variation on that use statement (::List, crate::List, List, no use statement) fixes the error if I leave the main function there.
Question: Can the use statement be fixed while the main remains there? If not why is the main function there at all?
Can the use statement be fixed while the main remains there?
No, super will refer to the encompassing module, not the function scope. There is no path that can name it as far as I'm aware. You could wrap everything within main in another module, so super can find it that way:
fn main() {
mod inner {
struct List;
mod tests {
use super::List;
}
}
}
If not why is the main function there at all?
mdBook just mimics the behavior of rustdoc which does wrap code samples with fn main() if not provided. This is not typically a problem since rustdoc examples wouldn't normally have the typical mod tests structure. Its unfortunate that this isn't handled though.
Specifically, I'm trying to put macro output into a doc comment. I was excited that this does exactly what I want:
/// foo
///
#[doc="bar\n\nbaz"]
///
/// quux
struct Dummy;
The next step would be to replace that string with my content. According to this, I can't write #[doc=my_content!()], and attribute macros are procedural, so I need another crate for that, plus (I think) my content could be generated without the need for any procedural macro features.
Is there a way to do this with "conventional macros" in some way, or am I out of luck?
Edit: starting with 1.54.0, Attributes can invoke function-like macros, enabling the code proposed in the question. Original answer below :
The answer seems to be no.
Looking at the grammar for attributes, apart from parentheses, commas and equals signs, attributes can ultimately only contain literals. So on this level, there is no way Rust allows more here.
However, inverting the structure enables something like this, and the doc-comment crate does this for doc comments. Instead of calling a macro from inside an attribute, use a macro to create an attribute; that macro is then not constrained to only taking literals*. The downside is, the item that the attribute should apply to must be part of the macro call. So this
#[doc=my_content!()]
struct Foo;
becomes this:
doc_comment!(
my_content!(),
struct Foo;
);
The definition of the macro is straight-forward:
#[macro_export]
macro_rules! doc_comment {
($x:expr, $($tt:tt)*) => {
#[doc = $x]
$($tt)*
};
}
(omitted a branch of the original macro that is not part of the core pattern)
(thanks to jonas-schlevink for pointing me to this)
*except for that last part (getting macro content into the attribute), the linked question's answer already does exactly that.