Question on `syn` crate. Why does this work?

I'm trying to figure out why this works or rather how it works?

parse_terminated is an associated function assigned to the variable-name parser. How are we able to call the parse function on it? Or is parser a sort of function type for which we automatically implement the Parser trait by calling . operator on parser


#![allow(non_snake_case)]

use proc_macro::TokenStream;
// use quote::quote;
use syn::parse::Parser;
// use syn::parse_macro_input;
use syn::punctuated::Punctuated;
use syn::{Expr, Token};

#[proc_macro]
pub fn ConvertTo(input: TokenStream) -> TokenStream {
    // Code Parse Phase
    // println!("{:?}", input);
    let tokens = input.clone();
    let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
    let buff = match parser.parse(tokens) {
        Ok(v) => v,
        Err(_e) => panic!(),
    };
    println!("{:#?}", buff);

    TokenStream::new()
}

According to the syn documentation, Parser is automatically implemented for every function from ParseStream to Result.

Ok, this was my assumption too but I got confused when the parse method was called on a function (called parser in my code) and not a type (like a struct, enum etc.). So, would I be right in saying that parser is a function object/type for which the Parser trait is automatically implemented

Functions have types too. fn(T) -> U is a function type, for example. Closures are just structs that implement some of the Fn(…) -> … traits. And so forth.

Functions aren't special. A function is just a function-typed value. You can do whatever you want with them. You can pass them around, store them in variables, and call methods on them.

(Incidentally, how would the compiler deal with functions if they didn't have a type?)

Being able to call methods on functions is no more surprising than calling methods on primitives. Some languages have artificial limitations in this regard (e.g. C++), but Rust is much more regular, and generally permits treating all types uniformly.

1 Like

Thank you. This helps!

To be fair, this is one part of ::syn's API that is a bit obscure / hard to spot, you're not the first one troubled by closures implementing a trait with an associated method :wink:

Since the whole procedural macros world is already quite dense for those starting with it, I think that discoverability is paramount. I would thus personally find quite useful if the parse... convenience functions at the top of the module were accompanied with parse..._with functions, such as:

fn parse_with<R, F> (
    tokens: TokenStream,
    parser: F, 
) -> Result<T>
where
    F : FnOnce(ParseStream<'_>) -> Result<T>,
{
    use ::syn::parse::Parser;

    parser.parse(tokens)
}
  • (accompanied with example using a syn type with multiple ways to be parsed, such as Punctutated sequences or paths).

  • cc @dtolnay, what do you think?

The code from the OP could then become:

let buff =
    match syn::parse_with(
        tokens,
        Punctuated::<Expr, Token![,]>::parse_terminated,
    )
    {
        Ok(it) => it,
        Err(e) => return e.to_compile_error().into(),
    }
};
  • Also, FWIW: when parsing fails, the returned Error bundles a convenience .to_compile_error().into() that generates a TokenStream that, when processed by Rust, displays a beautiful error message, much nicer than proc-macro panicked:

    - Err(_e) => panic!(),
    + Err(e) => return e.to_compile_error().into(),
    
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.