macroweave 0.1.0

Procedural macros for small compile-time repetitions over Rust token sequences.
Documentation
  • Coverage
  • 100%
    3 out of 3 items documented1 out of 3 items with examples
  • Size
  • Source code size: 41.11 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 318.21 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 4s Average build duration of successful builds.
  • all releases: 4s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • fast/macroweave
    9 1 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • andylokandy tisonkun

macroweave

Crates.io Documentation MSRV 1.85 Apache 2.0 licensed Build Status

macroweave provides procedural macros for generating repeated Rust code from compact, table-driven inputs.

Motivation

macroweave is for repetition that has to become Rust syntax, not runtime control flow. You write the choices once, name the columns, and use those names in Rust syntax.

That is the table-driven case macroweave is built around:

use macroweave::repeat;

trait ReadLe {
    fn read_le(input: &[u8]) -> Self;
}

repeat!((Ty, Width) in [
    (u16, 2),
    (u32, 4),
    (u64, 8),
] {
    impl ReadLe for Ty {
        fn read_le(input: &[u8]) -> Self {
            Ty::from_le_bytes(input[..Width].try_into().unwrap())
        }
    }
});

assert_eq!(u16::read_le(&[0x34, 0x12]), 0x1234);
assert_eq!(u32::read_le(&[1, 0, 0, 0]), 1);

This cannot be written as an ordinary for-loop because Ty and Width need to be substituted as tokens before the generated code is type-checked.

Whole-body repetition

Without splice syntax, [repeat!] emits the whole body once per input row:

use macroweave::repeat;

trait TypeName {
    const NAME: &'static str;
}

repeat!((Ty, Name) in [
    (u8, "u8"),
    (u16, "u16"),
    (u32, "u32"),
] {
    impl TypeName for Ty {
        const NAME: &'static str = Name;
    }
});

assert_eq!(<u8 as TypeName>::NAME, "u8");
assert_eq!(<u16 as TypeName>::NAME, "u16");
assert_eq!(<u32 as TypeName>::NAME, "u32");

Partial repetition

When only part of a surrounding construct should repeat, use [splice!] and put that part in #( ... )*. A single separator can be written before *, such as #( ... ),* for comma-separated output:

use macroweave::splice;

fn keyword_code(text: &str) -> Option<u8> {
    splice!((Pat, Code) in [
        ("async", 1u8),
        ("await", 2u8),
    ] {
        match text {
            #(Pat => Some(Code)),*,
            _ => None,
        }
    })
}

assert_eq!(keyword_code("async"), Some(1));
assert_eq!(keyword_code("await"), Some(2));
assert_eq!(keyword_code("fn"), None);

Placeholders are substituted only inside the splice body, and the surrounding tokens are emitted once. Surrounding identifiers stay literal, even when they have the same name as a placeholder. If a value should vary, place it in the splice body.

#( ..., )* and #( ... ),* are different: the latter does not produce a trailing comma. This matches delimiter repetition in macro_rules!.

Syntax notes

  • Bind placeholders as bare identifiers, such as Ty or Name.
  • Tuple rows bind multiple placeholders, and _ skips a row value.
  • Row values can contain one or more Rust tokens. Top-level commas separate rows.
  • Nested invocations are supported. Use different placeholder names at each level.

Minimum Rust version policy

This crate's minimum supported rustc version is 1.85.0.

The minimum Rust version can be increased in minor version updates. For example, if crate 1.0 requires Rust 1.85.0, then all 1.0.z releases also support Rust 1.85.0 or newer, while 1.y for y > 0 may require a newer compiler.

License

This project is licensed under Apache License, Version 2.0.

Origins

macroweave resulted from a ScopeDB code refactor. ScopeDB has used match-template for variant/type match arms and macro_find_and_replace for repeating Rust fragments over type lists. While reviewing their macro usages, I found that I wanted the same thing in both places: write the choices once, name the columns, and use those names in Rust syntax. There was no existing solution fitting that shape.