1 unstable release
0.1.0 | Aug 22, 2022 |
---|
#752 in Procedural macros
13KB
152 lines
accio
This crate is mostly an experiment, and is not intended for production!
accio
retrieves code blocks distributed to multiple sites in the build path,
and puts them together in the call site.
It does this at the compile time; therefore no code execution takes place in
load-time (ctor
) or runtime (any kind of lazy initialization).
It allows for partial implementations of functions, enums, struct fields, match statements and more.
There are still many quirks with it; and they are not likely to be solved in the future. Just to name a few;
- Macro execution paths are not checked AT ALL,
- Conditional compilation flags are blatantly disregarded,
- Incremental compilation might cause some problems with when the macros are re-evaluated,
- Error messages will point to incorrect places,
- The
accio_emit
macro is only matched by name; and name overlaps are not checked for, - None of the macros in this crate can be nested,
- There is no way for an
accio_emit
statement to dynamically refer to its own path, - The source path is simply recursively traversed for
.rs
files; instead of verifying that any of these files are imported somewhere, - There are many possible errors during parsing that are silently omitted,
- It does not work with documentation examples! (Yes, every example in the docs is
no_run
.) - I simply didn't test how it would behave with multiple crates.
For most use-cases; I can recommend inventory
, linkme
, or ctor
crates.
For automatically importing all modules in a directory, you can use automod
.
Contribution
I'm unlikely to respond to issues in this crate; but if you think you can make this usable in any way; go for it. So PRs are welcome I guess.
Use cases
accio
can be used for collecting statements in a function body as follows:
use accio::*;
// some_file.rs
accio_emit! {
// accio_emit's block must be written
// as `scope_name { code_block }` pairs.
// multiple blocks can be listed as follows:
first_scope {
val = 1;
}
second_scope {
val += 2;
}
}
// some_other_file.rs
accio_emit! {
// the same scope name can have multiple
// blocks; these are merged in an
// undetermined order.
second_scope {
val += 3;
}
}
// another_file.rs
let mut val = 0;
assert_eq!(val, 0);
// include the first scope:
// this will evaluate to `val = 1;`
accio!(first_scope);
assert_eq!(val, 1);
// include the second scope:
// this will evaluate to either
// `val += 2; val += 3;` or
// `val += 3; val += 2;`.
accio!(second_scope);
assert_eq!(val, 6);
A more meaningful use case is automatically gathering enum variants.
However, due to Rust's grammar rules, the accio!
macro cannot be
placed inside the braces of an enum or a struct. The following would
not compile:
use accio::*;
enum SomeEnum {
accio!(enum_variants)
}
This would yield:
error: expected one of `(`, `,`, `=`, `{`, or `}`, found `!`
--> src/lib.rs:60:10
|
5 | accio!(enum_variants)
| ^ expected one of `(`, `,`, `=`, `{`, or `}`
Instead, we can use the accio_body
attribute macro:
use accio::*;
#[accio_body(enum_variants)]
enum SomeEnum {
// this part MUST be empty!
}
#[accio_body(struct_fields)]
struct SomeStruct {
/* this part MUST be empty! */
}
#[accio_body(array_elems)]
static SOME_ARRAY: &[i32] = &[];
accio_emit! {
enum_variants {
FirstVariant,
}
enum_variants {
SecondVariant(String),
}
struct_fields {
pub name: String,
}
array_elems: {
42,
}
}
// ...and so on
Note that accio_body
implementation places the code into the
first empty curly brace ({}
) or square bracket ([]
) scope.
Therefore the following variants will fail:
use accio::*;
#[accio_body(enum_variants)]
enum FailingEnum {
// there is code within the braces
AlreadySomeVariant,
}
#[accio_body(struct_fields)]
struct FailingStruct; // no braces!
You can still add comments, they do not cause issues.
See examples/
for more detailed examples.
Dependencies
~1.5MB
~35K SLoC