0

I was sitting typing a ton of repetitive code and thought - I should write a macro to do this. Then I realized that what I need cannot be done with the type of macro that I know how to write.

Here is what I need (it's describing the location of bits in a u16):

const XXX_OFF: u16 = n;
const XXX: Word = 1 << XXX_OFF;

e.g.

const RTS_OFF: u16 = 0;
const RTS: Word = 1 << RTS_OFF;

const CTS_OFF: u16 = 3;
const CTS: Word = 1 << CTS_OFF;

// ...

declaring the offset and generating a bitmask for each named bit in a u16.

As you can see the only things that change are XXX and n. Is there anyway I could automate this? It would be trivial in C. I wonder if there is some form of template driven proc macro out there. Or if there is another way of achieving the same thing. Maybe I have to go learn how to write proc macros.

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
pm100
  • 48,078
  • 23
  • 82
  • 145
  • 1
    If you use a declarative macro in Rust, you need to parametrize it with `n`, `XXX` _and_ `XXX_OFF`, since there is no way to dynamically create identifiers in a delcarative macro. – Sven Marnach Aug 22 '20 at 18:36
  • @SvenMarnach even then its not doable, a declarative macro cannot generate new items in its callers namespace -> hygiene – pm100 Aug 22 '20 at 18:57
  • @pm100 Sure you can. That's not how macro hygiene works in Rust. Simply try it, and you'll see it works. – Sven Marnach Aug 22 '20 at 19:09
  • For what it's worth, the `_OFF` constants aren't really needed. You can use `XXX.trailing_zeros()` instead. The `trailing_zeros()` function is `const`, so this will be evaluated at compile time. – Sven Marnach Aug 22 '20 at 19:12
  • @SvenMarnach - i actually built my solution using your suggestions a) you can do it just not generate new names b) use trailing_zeros – pm100 Aug 22 '20 at 22:04

2 Answers2

2

The paste crate is exactly the "code template" tool you're looking for. As of writing, 1.0.0 isn't available on the playground, so I'm using 0.1.18:

extern crate paste; // 0.1.18
type Word = u16;

macro_rules! bitfield_with_offset {
    ($name:ident, $bit:expr) => {
        paste::item! {
        const [<$name _OFF>]: u16 = $bit;
        }
        const $name:  Word = 1 << $bit;
    }
}

bitfield_with_offset!(ASD, 5);

fn main() {
    println!("{}, {}", ASD_OFF, ASD);
}

This will output:

5, 32

Playground link

It seems 1.0.0 removes the item! macro for a more flexible generic paste! macro, so your solution may look a little different.

Lytigas
  • 904
  • 1
  • 7
  • 16
1

A rather dirty solution (from this answer) would be to create a module with the name of e.g. RTS and put a public constant OFF in it, so you can access it as RTS::OFF.

Playground

macro_rules! define_word {
    ($name:ident, $bit:expr) => {
        mod $name {
            pub const OFF: u16 = $bit;
        }
        const $name: Word = 1 << $bit;
    };

}

define_word!(RTS, 0);
define_word!(CTS, 3);

fn main() {
    println!("{} {}", RTS::OFF, RTS);
    println!("{} {}", CTS::OFF, CTS);
}
Alexey S. Larionov
  • 6,555
  • 1
  • 18
  • 37
  • thats pretty good. Feels to me like there is a space for a proc macro that can take a code template and fill in the blanks. As a generic tool – pm100 Aug 22 '20 at 21:48