Expand description
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!
macro from the nightly Rust, which is limited in capabilities and has not been stabilized
since 2015.
compose_idents::compose_idents!(
my_fn = concat(foo, _, "baz"),
{
fn my_fn() -> u32 {
42
}
},
);
assert_eq!(foo_baz(), 42);
§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 the
hash()
function, which is seeded uniquely for each invocation of the macro. This might be useful for generating unique global variables.
§Usage
This section contains various usage examples. For additional examples, see the 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:
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:
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 = concat(arg1, normalize(arg2), ...)
, alias = lower(ARG)
,
alias = arg
, etc., where args may be identifiers, string literals, integers, underscores, or any arbitrary sequences
of tokens (like &'static str
, My::Enum
and so on - such values would be recognized as just tokens):
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:
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:
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:
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:
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:
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.
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:
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:
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
Function | Description |
---|---|
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:
warning: use of deprecated function `my_function`: compose_idents!: Using semicolons as separators is deprecated, use commas instead
§Alternatives
There are 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:
Macros§
- compose_
idents - Compose identifiers from the provided parts and replace their aliases in the code block.