Bring custom derive macro into namespace via plugin - rust

Is it possible to reduce the boilerplate in using a library (as the library author) from:
#![feature(plugin)]
#![plugin(myplugin)]
#[macro_use]
extern crate myplugin_derive;
#[derive(MyPlugin)]
// ...
to something more like:
#![feature(plugin)]
#![plugin(myplugin)]
#[derive(MyPlugin)]
// ...
That is, since I know anyone using myplugin wants to use #[derive(MyPlugin)], is there a way of bringing MyPlugin into scope with #![plugin(myplugin)] only?

Related

Conditional compilation based on proc_macro

Is it possible to somehow detect in a crate that it's used in proc_macro and include some items based on that?
Let's say I have a token_parser crate with a Token enum:
pub struct Group;
pub struct Ident;
pub struct Punct;
pub struct Literal;
pub enum Token {
Group(Group),
Ident(Ident),
Punct(Punct),
Literal(Literal)
}
It's intended to be a wrapper for a proc_macro::TokenTree, so I want to be able to use it inside a proc_macro crates. I don't want to manualy implement a converting logic in every crate, so I want something like this:
#[cfg(proc_macro)]
impl From<proc_macro::TokenTree> for Token {
fn from(token: proc_macro::TokenTree) -> Self
{
todo!()
}
}
while still having an option to use token_parser crate in normal crates.
P.S I'm aware of proc_macro2 crate. I will probably use it (or even syn) in the end, for now I'm just trying to understand how to do something like that myself properly.
There are primarily two ways I would reach for based on how I want to enable such functionality.
feature for project-based enabling
If you want it only available when opted for, that's a fantastic case to use a cargo feature. Crates that want to use such functionality can then choose to enable this feature while other crates can choose to not enable the feature. Whether this feature should be default typically depends on how the library author believes it should/is used most of the time.
cfg for platform-based enabling
As you may have seen from reading the proc_macro2 source, they conditionally enable a cfg in their build script1. On the wasm32 platform target it is disabled, otherwise it is enabled. This way is very useful in cases where enabling functionality mostly depends on the platform.

Project structure in rust -- adding extra files

I am learning rust and am super confused at the project structure for cargo projects. I am using this page (and the rust book) for reference.
I am trying to separate my project for different structs so they can have their own file, similar to cpp and classes.
so if I have
src/main.rs
src/struct_name.rs
and I create the mod for it:
pub mod struct_name
{
pub struct _struct_name
{}
}
I should be able to do this in main.rs:
mod struct_name;
But the problem I am having is when I am trying to include external crates. For example, I'm trying to implement clap and use macros. So I do the following in igloo.rs:
#[macro_use]
extern crate clap;
use clap::{Arg, App};
pub mod struct_name
{
pub struct _struct_name
{//do clap things}
}
I am given the error E0468 "an 'extern crate' loading macros must be at the crate root". Does this mean I can never use macros outside of main.rs or lib.rs? How can I somehow export these functions so that I can use them in struct_name.rs? I get that I could make export functions in main.rs or lib.rs, but if I am using the mod in main.rs and lib.rs, then struct_name.rs would never see the exported functions, right?
In main.rs:
Macros imported at the top of the crate are avaliable everywhere in the crate.
#[macro_use]
extern crate clap;
mod struct_name
In struct_name.rs:
You only need to define a module in its parent.
use clap::{Arg, App};
// do things

What's the best way to use `test::TestDescAndFn` without using libtest?

I'm working on a custom test-runner for a custom target, that doesn't have libtest available. However I would like to use the TestDescAndFn struct, which is defined in libtest, in my own test-runner. TestDescAndFn provides information about the tests such as if it should panic or not, the name of the test and others, so having this information would be really useful compared to just using #[test_case].
Since the #[test] annotation is resolved at compile-time and simply generates a test harness that calls a (custom) test-runner I can define with #![test_runner(my_test_runner)], I don't think I really need libtest aside from TestDescAndFnand the enums it contains. Is there any "good" way to use the definitions from libtest for TestDescAndFn, TestDesc etc., without actually building libtest?
The test crate provides TestDescAndFn and is provided by the compiler. So, your custom framework crate might do something like this:
#![feature(test)]
// provided by compiler, no need to specify dependency in Cargo.toml
extern crate test;
use ::test::TestDescAndFn;
pub fn run_tests(tests: &[&TestDescAndFn]) {
// ...
}
and then your crate under test would do this:
#![feature(custom_test_frameworks)]
#![test_runner(::my_framework::run_tests)]
// crate with #[test] as normal

What does #[cfg(test)] do when placed at the top of lib.rs?

I'm writing a Rust library (generated from cargo) with unit tests.
I'd like to use the extern crate maplit in my unit tests to be able to use JavaScript-like hashmap literals. I don't want to use maplit in my library code.
maplit provides a macro that apparently must be activated using #[macro_use]. The only way I've been able to get this all working is to place this at the top of lib.rs:
#[cfg(test)] #[macro_use] extern crate maplit;
// my crate-specific stuff
At this point I realized I don't know what exactly #[cfg(test)] does. I'm using it in my tests. These are included with library code, as per the convention, like so:
// some library code
#[cfg(test)]
mod test {
use super::*;
// tests here
}
I had thought that the line #[cfg(test)] was marking what follows until the end of the file (or block?) as only applicable to the test configuration.
If so, then putting this directive at the top of lib.rs seems like a problem. Won't my entire library be dropped when I compile a distribution?
I've tried to find documentation on what exactly #[cfg(test)] does, but to no avail.
#[....]
The above is a Rust Attribute which is like an annotation in other languages. For example; in Java we have #Annotation(....) for methods and classes. Unlike annotation the rust attribute can be an expression that follows the attribute syntax.
#[cfg(....)]
The above is a compiler configuration attribute. The cfg() is one of many built-in attributes.
#[cfg(test)]
The above tells the Rust compiler that the following code should only be compiled when the test configuration is active. You can have other configuration attributes like debug, windows or features.
#[cfg(test)] #[macro_use] extern crate maplit;
Is the same as
#[cfg(test)]
#[macro_use]
extern crate maplit;
Which tells the Rust compiler to only compile the next line if the test configuration is active, and the next line tells Rust to only use macros from the following crate.
If so, then putting this directive at the top of lib.rs seems like a problem. Won't my entire library be dropped when I compile a distribution?
The #[cfg(...)] attribute only applies the compiler condition upon the thing it is attached to.
When you place the attribute at the top of the file followed by a space. The attribute is attached to the current module or crate.
As shown here from the documentation example, the crate_type is applied to the entire file:
// General metadata applied to the enclosing module or crate.
#![crate_type = "lib"]
// A function marked as a unit test
#[test]
fn test_foo() {
/* ... */
}
// A conditionally-compiled module
#[cfg(target_os = "linux")]
mod bar {
/* ... */
}
// A lint attribute used to suppress a warning/error
#[allow(non_camel_case_types)]
type int8_t = i8;
// Inner attribute applies to the entire function.
fn some_unused_variables() {
#![allow(unused_variables)]
let x = ();
let y = ();
let z = ();
}

How do you refer to functions in a crate from an exported crate macro in rust?

I'm trying to write some debugging helper macros. So I create a crate to hold all of them, and referred to the crate externally using phase(plugin):
#[cfg(test)]
#[phase(plugin)]
extern crate debugging;
That successfully exports the macros into the crate.
However, for some of these tools I wanted to invoke functions from the crate, for example:
#[macro_export]
macro_rules! trace(
{$($arg:tt)*} => {
{
extern crate debug;
let s = ::macros::logger(); // <---- This!
let _ = s.write_line(format_args!(::std::fmt::format, $($arg)*).as_slice());
}
};
)
Using a global namespace, ie. ::macros::logger works if the macro is placed into macros.rs and imported from the crate root of a crate.
However, what I'm trying to achieve here is the same thing when the macro has to invoke a call from its own crate.
If the macro expanded correctly, I believe:
let s = ::crate_name::logger();
Would be the way to do it, but the crate with the macro cannot refer to itself by name in this way. It generates errors like:
src/macros.rs:52:15: 52:20 error: failed to resolve. Maybe a missing `extern crate debugging`?
src/macros.rs:52 let s = ::debugging::logger();
^~~~~
src/macros.rs:49:1: 56:2 note: in expansion of trace!
src/file.rs:26:11: 26:29 note: expansion site
...and finally just so we're completely clear about what I'm trying to do:
crate debugging:
lib.rs
fn logger()
macros.rs
macro_rules! trace(...) // <--- Trying to figure out what to put here...
crate foo:
lib.rs
#[phase(plugin)] extern crate debugging;
fn test_thing() {
trace!("{}", ...); // <--- So debugging::logger() is invoked here
}
So, is there some way to do this from a macro?
For now, the correct thing to do is use an absolute path like that and then do some reexports in the original crate so that path resolves.
I.e. in lib.rs put
mod debugging {
pub use logger;
}
which reexports the top level function logger as debugging::logger. There is work being done that makes this saner, e.g. $crate which expands to name of the crate from which the macro came (when used externally), or to nothing (when used internally); this isn't necessarily the final design, but it is improving.
For anyone else who finds this, it's worth noting that you cannot use from a crate marked with phase plugin. To import the symbols locally (as per the accepted answer), you need to do this:
#[cfg(test)]
mod debug {
#[phase(plugin)]
extern crate debugging;
extern crate debugging;
pub use self::debugging::debug;
}
Notice the double extern crate debugging, because typically you wouldn't want to export a syntax extension module as part of the code.

Resources