
[](https://crates.io/crates/compose-idents)
[](https://docs.rs/compose-idents)
# compose-idents
A macro for generating new identifiers (names of variables, functions, traits, etc.) by concatenating one or more
arbitrary parts and applying other manipulations.
It was created as an alternative to `macro_rules!` that doesn't allow creating new identifiers from the macro arguments
and [`concat_idents!`][1] macro from the nightly Rust, which is limited in capabilities and has not been stabilized
[since 2015][2].
```rust
compose_idents::compose_idents!(
my_fn = concat(foo, _, "baz"),
{
fn my_fn() -> u32 {
42
}
},
);
assert_eq!(foo_baz(), 42);
```
[1]: https://doc.rust-lang.org/std/macro.concat_idents.html
[2]: https://github.com/rust-lang/rust/issues/29599
## Features
- **Identifier generation**
Identifiers can be generated via concatenation of multiple parts. Arguments of the outer macro
definitions and literals are supported for identifier definitions.
- **Functions**
Functions can be applied when defining new identifiers for changing case and style.
- **String formatting**
Strings can be formatted with `%alias%` syntax, which is useful for generating doc-attributes.
- **Unique identifier generation**
Unique identifiers can be deterministically generated by using `hash()` function which is uniquely
seeded each invocation of the macro. This might be useful for generating unique global variables.
## Usage
This section contains various usage examples. For even more usage examples look into `tests/` directory
of the repository.
### Quick start
`compose_idents!` works by accepting definitions of aliases and a code block where aliases
could be used as normal identifiers. When the macro is expanded, the aliases are replaced with their
definitions:
```rust
use compose_idents::compose_idents;
// We generate separate const-functions for each type as a workaround
// since Rust doesn't allow us to use `core::ops::Add` in `const fn`.
macro_rules! gen_const_add {
($T:ty) => {
compose_idents!(
Type = upper($T), // Alias for the type - make it uppercase in addition
add_fn = concat(add_, $T), // Alias for the function name
{
// Strings (including in doc-attributes) can be formatted with %alias% syntax.
#[doc = "Adds two arguments of type `%Type%` at compile time."]
pub const fn add_fn(a: $T, b: $T) -> $T { // Aliases are used as normal identifiers
a + b
}
}
);
};
}
gen_const_add!(u32); // Expands into `add_u32()` function.
gen_const_add!(u64); // Expands into `add_u64()` function.
assert_eq!(add_u32(2_u32, 2_u32), 4_u32);
assert_eq!(add_u64(2_u64, 2_u64), 4_u64);
```
### Generating tests for different types
A practical example for how to auto-generate names for macro-generated tests for different data types:
```rust
use compose_idents::compose_idents;
pub trait Frobnicate {
type Output;
fn frobnicate(&self, value: Self) -> Self::Output;
}
impl Frobnicate for u32 {
type Output = u32;
fn frobnicate(&self, value: Self) -> Self::Output {
self + value
}
}
impl Frobnicate for &'static str {
type Output = String;
fn frobnicate(&self, value: Self) -> Self::Output {
format!("{}_{}", self, value)
}
}
macro_rules! generate_frobnicate_test {
($type:ty, $initial:expr, $input:expr, $expected:expr) => {
// Notice - we are using normalize() to make `&'static str` fit for
// being part of the test function's identifier.
compose_idents!(test_fn = concat(test_frobnicate_, normalize($type)), {
fn test_fn() {
let actual = ($initial as $type).frobnicate($input);
let expected = $expected;
assert_eq!(actual, expected);
}
});
};
}
// Generates tests for u32 and &'static str types
generate_frobnicate_test!(u32, 0, 42_u32, 42_u32);
generate_frobnicate_test!(&'static str, "foo", "bar", "foo_bar".to_string());
test_frobnicate_u32();
// Notice - "&'static str" has been turned into just "static_str"
test_frobnicate_static_str();
```
## Reference
This is a complete reference to the functionality of this library split into thematic sections.
### Basic alias definition
You can define aliases with the syntax `alias = [arg1, arg2, …]`, where args may be identifiers, string literals,
integers, underscores, or any arbitrary sequences of tokens (like `&'static str`):
```rust
use compose_idents::compose_idents;
compose_idents!(
// Literal strings are accepted as arguments and their content is parsed.
my_fn_1 = concat(foo, _, "bar"),
// The same applies to literal integers, underscores or free-form token sequences.
my_fn_2 = concat(spam, _, 1, _, eggs),
{
fn my_fn_1() -> u32 {
42
}
fn my_fn_2() -> u32 {
42
}
},
);
assert_eq!(foo_bar(), 42);
assert_eq!(spam_1_eggs(), 42);
```
### Alias reuse
Aliases could also be reused in definitions of other aliases:
```rust
use compose_idents::compose_idents;
compose_idents!(
base_alias = FOO,
derived_alias = concat(BAR, _, base_alias),
{
static base_alias: u32 = 1;
static derived_alias: u32 = base_alias;
},
);
assert_eq!(FOO, 1);
assert_eq!(BAR_FOO, 1);
```
### Functions
Functions can be applied to the arguments used for the alias definitions:
```rust
use compose_idents::compose_idents;
compose_idents!(
my_const = concat(upper(foo), _, lower(BAR)),
// Function calls can be arbitrarily nested and combined.
my_static = upper(lower(BAZ)),
{
const my_const: u8 = 1;
static my_static: &str = "hello";
}
);
assert_eq!(FOO_bar, 1);
assert_eq!(BAZ, "hello");
```
You can find a complete description of all functions below under "Functions" heading.
### Casing manipulation
There are multiple functions for altering the naming convention of identifiers:
```rust
use compose_idents::compose_idents;
compose_idents!(
MY_SNAKE_CASE_STATIC = snake_case(snakeCase),
MY_CAMEL_CASE_STATIC = camel_case(camel_case),
MY_PASCAL_CASE_STATIC = pascal_case(concat(pascal, _, case)),
{
static MY_SNAKE_CASE_STATIC: u32 = 1;
static MY_CAMEL_CASE_STATIC: u32 = 2;
static MY_PASCAL_CASE_STATIC: u32 = 3;
},
);
assert_eq!(snake_case, 1);
assert_eq!(camelCase, 2);
assert_eq!(PascalCase, 3);
```
### Token normalization
`normalize()` function is useful for making valid identifiers out of arbitrary tokens:
```rust
use compose_idents::compose_idents;
compose_idents!(
MY_NORMALIZED_ALIAS = concat(my, _, normalize(&'static str)),
{
static MY_NORMALIZED_ALIAS: &str = "This alias is made from a normalized argument";
}
);
assert_eq!(
my_static_str,
"This alias is made from a normalized argument"
);
```
### String formatting
Aliases could be used in string formatting with `%alias%` syntax. This is useful for generating doc-attributes:
```rust
use compose_idents::compose_idents;
compose_idents!(
my_fn = concat(foo, _, "baz"),
MY_FORMATTED_STR = concat(FOO, _, BAR),
{
static MY_FORMATTED_STR: &str = "This is %MY_FORMATTED_STR%";
// You can use %alias% syntax to replace aliases with their definitions
// in string literals and doc-attributes.
#[doc = "This is a docstring for %my_fn%"]
fn my_fn() -> u32 {
321
}
},
);
assert_eq!(FOO_BAR, "This is FOO_BAR");
```
### Generating unique identifiers
`hash()` function deterministically hashes the input _within a single macro invocation_. It means that within the same
`compose_idents!` call `hash(foobar)` will always produce the same output. But in another call - the output would be
different (but also the same for the same input).
It could be used to avoid conflicts between identifiers of global variables, or any other items that are defined in
global scope.
```rust
use compose_idents::compose_idents;
macro_rules! create_static {
() => {
compose_idents!(
MY_UNIQUE_STATIC = hash(1),
MY_OTHER_UNIQUE_STATIC = hash(2),
{
static MY_UNIQUE_STATIC: u32 = 42;
static MY_OTHER_UNIQUE_STATIC: u32 = 42;
}
);
};
}
create_static!();
create_static!();
```
This example roughly expands to this:
```rust
use compose_idents::compose_idents;
static __5360156246018494022: u32 = 42;
static __1421539829453635175: u32 = 42;
static __17818851730065003648: u32 = 42;
static __10611722954104835980: u32 = 42;
```
### Concatenating multiple arguments
The `concat()` function takes multiple arguments and concatenates them together. It provides explicit concatenation
that can be either nested within other function calls or to aggregate results of other function calls:
```rust
use compose_idents::compose_idents;
compose_idents!(
// Basic example
basic_fn = concat(foo, _, bar, _, baz),
// Mixed with other functions
upper_fn = upper(concat(hello, _, world)),
// Complex example
complex_fn = concat("prefix_", normalize(&'static str), "_", snake_case(CamelCase)),
{
fn basic_fn() -> u32 { 1 }
fn upper_fn() -> u32 { 2 }
fn complex_fn() -> u32 { 3 }
}
);
assert_eq!(foo_bar_baz(), 1);
assert_eq!(HELLO_WORLD(), 2);
assert_eq!(prefix_static_str_camel_case(), 3);
```
### Functions
| `upper(arg)` | Converts the `arg` to upper case. |
| `lower(arg)` | Converts the `arg` to lower case. |
| `snake_case(arg)` | Converts the `arg` to snake_case. |
| `camel_case(arg)` | Converts the `arg` to camelCase. |
| `pascal_case(arg)` | Converts the `arg` to PascalCase. |
| `normalize(tokens)` | Transforms a free-form sequence of `tokens` into a valid identifier. |
| `hash(arg)` | Hashes the `arg` deterministically within a single macro invocation. |
| `concat(arg1, arg2, ...)` | Concatenates multiple arguments into a single identifier. |
## Deprecation policy
- As a general rule old functionality is not removed abruptly, but rather deprecated first and removed after
a few releases. This applies to pre-1.0.0 releases as well.
- Deprecation works by injecting `#[deprecated]` attribute to existing syntactic elements of generated code
without adding new ones. It might not work in corner cases if there is no place where the attribute could be added.
Here is how a deprecation warning might look like:
```text,ignore
warning: use of deprecated function `my_function`: compose_idents!: Using semicolons as separators is deprecated, use commas instead
```
## Alternatives
There some other tools and projects dedicated to identifier manipulation:
- A macro from Nightly Rust that allows to concatenate identifiers. It is limited in functionality and nowhere near
to be stabilized:
<https://doc.rust-lang.org/std/macro.concat_idents.html>
- A very similar macro that doesn't support multiple aliases and is not maintained:
<https://github.com/DzenanJupic/concat-idents>
- A macro that allows to define and refer to unique temporary variables:
<https://crates.io/crates/templest>
## Development
The following standards are followed to maintain the project:
- <https://www.conventionalcommits.org/en/v1.0.0/>
- <https://semver.org/>
- <https://keepachangelog.com/en/1.1.0/>
- <https://adr.github.io/madr/>