Matching keywords in declarative macros with `empty` macro fragments OR named repetition groups

Currently the only(?) way to detect the presence of an optional or repeated keyword is by abusing the vis macro fragment, since it may be empty.

For example, a macro that optionally adds an unsafe modifier to the generated fn item:

macro_rules! my_closure_like_macro {
  {
    $($is_unsafe:vis unsafe)? |
      $($arg0_name:ident: $arg0_ty:ty $(, $argn_name:ident: $argn_ty:ty)*)?
    | $(-> $ret_ty:ty)? $body:expr
  } => {
    // actually produce a fn item instead of a normal closure :)
    {
      $($is_unsafe unsafe)? fn anon(
        $($arg0_name: $arg0_ty $(, $argn_name: $argn_ty)*)?
      ) $(-> $ret_ty)? {
        $body
      }
      anon
    }
  };
}

static MY_FFI_VTABLE: VTbl = VTbl {
  add_u64xn: my_closure_like_macro! {
    unsafe |ptr: *const u64, n: usize| -> u64 {
      // ... stuff here ...
    }
  },
};

One way to fix this issue is by adding an empty macro fragment specifier. $fragment:empty does not consume any tokens but can be used to expand repetitions of a capture group.

macro_rules! my_closure_like_macro {
  {
    $(unsafe $is_unsafe:empty)? |
      $($arg0_name:ident: $arg0_ty:ty $(, $argn_name:ident: $argn_ty:ty)*)?
    | $(-> $ret_ty:ty)? $body:expr
  } => {
    // ... no changes ...
  };
}

Another way to fix this is with explicit repetitions via named capture groups.

macro_rules! my_closure_like_macro {
  {
    $is_unsafe:(unsafe)? |
      $($arg0_name:ident: $arg0_ty:ty $(, $argn_name:ident: $argn_ty:ty)*)?
    | $(-> $ret_ty:ty)? $body:expr
  } => {
    // explicit expansion for all repetitions without having to use a capture
    $is_unsafe:(unsafe)? /* ... the rest is the same */
  };
}

This would also make other things easier, like counting the number of expressions passed into a macro:

macro_rules! my_vec {
  [ $items:($item:expr),* $(,)? ] => {
    {
      let mut my_vec = MyVec::with_capacity(0 $items:(+ 1)*); // no "ignore!"
      $(my_vec.push($item);)*
      my_vec
    }
  };
}

Counting will be handled by macro_metavar_expr, but I don’t think the unsafe use case will be.

named capture groups: https://github.com/rust-lang/rfcs/pull/3649

1 Like