Use `$t::method()` in macros cause error

I'm wrapping to_be_bytes into traits, here's the code:

macro_rules! impl_to_be_bytes_for {
    ($($t:ty)*) => ($(
        impl ToBytes for $t {
            fn to_be_bytes(&self) -> [u8; mem::size_of::<Self>()] {
                return $t ::to_be_bytes(*self);

and impl_to_be_bytes_for!(u32);

but the compiler reported an error:

12 |                 return $t ::to_be_bytes(*self);
   |                        ^^ expected one of `.`, `;`, `?`, `}`, or an operator

I found some rules on this page:

path and ty may only be followed by one of: => , , , = , | , ; , : , > , >> , [ , { , as , where , or a macro variable of block fragment specifier.

But in my case, since the function name in my trait is the same with to_be_bytes, I need to use format like u32::to_be_bytes.

So how could I write the proper code to make my macro works?

P.S. I enabled the "Constant Generic" feature, without this the code may not get compiled.

It's because not all types can be followed with the :: token. For example, return Option<i32>::foo(); is not a valid syntax. You can wrap the type with the <> to avoid this issue.

return <$t>::to_be_bytes(*self);

In addition to this, as a notational point, you may want to separate your types with a comma, i.e. $($t:ty),*.

1 Like

To expand on this: the Rust grammar is very strict about what "kinds" of stuff can be encountered where; and …::to_be_bytes(…), for instance, will only parse if …::to_be_bytes is a path to a value. And here is what the Rust reference regarding the Rust grammar has to say about this:

      • e.g.
        u32::to_be_bytes(…) :white_check_mark:
      • e.g.
        <u32>::to_be_bytes(…) :white_check_mark:
        <(u32)>::to_be_bytes(…) :white_check_mark:

So, basically, when writing …::to_u32_bytes(<some expr>), the before the :: can only be one of two things:

  • either a (::-delimited sequence of) PathExprSegment(s), i.e., a (sequence of) identifier(s) (with optional turbofish args), such as the u32 identifier,

  • Or a QualifiedPathInExpression, such as <u32> (more generally, a bracketed-delimited type (with an optional as Trait).

The surprise here is that when capturing a u32 within a macro metavariable/transcriber of the :ty kind, to then use it to emit the call expression:

let some_expression = …;
macro_rules! with_u32 {( $u32:ty ) => (
let bytes = with_u32!( u32 );

This fails, even though, it "ought" to be simply emitting a u32 :: to_be_bytes ( some_expression ), right?

Wrong! :x:

The subtle thing here is that the macro metavariable / transcriber captures which are not :tt, :ident, or :lifetime actually wrap the captured syntax within "invisible parenthesis" that make it no longer be something as simple as an identifier: the "real" expansion of that with_u32!( u32 ) call, with our invisible parenthesis goggles on, is:

   ⁽u32⁾ :: to_be_bytes ( some_expression )
// ^^^^^
// a type

which does not match the syntax PathInExpression syntax at all, and doesn't match the QualifiedPathInExpression syntax by that tiny detail of there not being the necessary angle brackets.

Hence why adding the angle brackets was necessary (a :ty macro metavariable/transcriber (such as ⁽u32⁾) is obviously a valid Type within Rust grammar).

Note that these "invisible" parenthesis are not shown on cargo expand / rustc -- … {un,}pretty=expanded output, hence why this error message may look especially confusing given that one can copy-paste the output of such expansion and the code will work.

In some cases, this is a reason to use $($T:tt)* to capture the $T as an arbitrary sequence of arbitrary tokens which are later on emitted verbatim, causing no issues except when generics parameters appear (in which case @Hyeonu's remark about a necessary turbofish syntax or <…> wrapping applies). But then we have the issue of it being quite hard to parse multiple such $Ts, since nothing can follow a :tt)* repetition, and thus each type needs to be grouped within parenthesis, brackets, or braces, to avoid ambiguity errors:

macro_rules! with_types {(
    $( ($($T:tt)*) )*
) => (
        let _ = $($T)* :: to_be_bytes ( some_expression );
    (Vec<String>) // Error, `Vec < String` expression followed by extraneous code

A more general solution to this issue is, since proc-macros can see and interact with those invisible parenthesis, to use a helper proc-macro which can strip them, such as:

  • Disclaimer: authored by me.
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.