Why `ParseBuffer::peek` in syn crate can accept type as argument?

Why could the peek function of ParseBuffer in syn crate accept types like Ident and Lifetime that are not zero-sized as argument? Should't only expressions be allowed in function arguments, or is this a special syntax that actually passes a value? Thanks.

This is the source code of peek. Seems nothing special.

pub fn peek<T: Peek>(&self, token: T) -> bool {
    let _ = token;
    T::Token::peek(self.cursor())
}

Function calls accept values, not types, as arguments. I'm not sure what this question is about, or how the declaration of peek() would contradict this. Are you perhaps confusing generic type parameters with "passing an argument"?

I think the confusion is based on the fact that this compiles:

fn check_peek(buf: syn::parse::ParseBuffer) {
    buf.peek(syn::Ident);
    buf.peek(syn::Lifetime);
}

...despite both syn::Ident and syn::Lifetime seemingly being types and not values.

To check what's really going on, let's ask the compiler: if you treat these two as values, allowing us to pass them as arguments, surely they have a type - what is this type, then? Standard trick for asking this - assign them to the place of wrong type:

fn check_types() {
    let _: () = syn::Ident;
    let _: () = syn::Lifetime;
}

Errors:

error[E0308]: mismatched types
 --> src/lib.rs:2:17
  |
2 |     let _: () = syn::Ident;
  |            --   ^^^^^^^^^^ expected `()`, found fn item
  |            |
  |            expected due to this
  |
  = note: expected unit type `()`
               found fn item `fn(syn::lookahead::TokenMarker) -> syn::Ident {syn::Ident}`

error[E0308]: mismatched types
 --> src/lib.rs:3:17
  |
3 |     let _: () = syn::Lifetime;
  |            --   ^^^^^^^^^^^^^ expected `()`, found fn item
  |            |
  |            expected due to this
  |
  = note: expected unit type `()`
               found fn item `fn(syn::lookahead::TokenMarker) -> syn::Lifetime {syn::Lifetime}`

So, both syn::Ident and syn::Lifetime are not only types, but also values - namely, function items (or just functions). And, indeed, there they are in source - here's Ident, here's Lifetime. They are both doc(hidden), so they don't appear in documentation[1].

As for why the function items can be used as Peek types - well, that's the only things that can, in fact, since there's only one implementation:

impl<F: Copy + FnOnce(TokenMarker) -> T, T: Token> Peek for F {
    type Token = T;
}

where TokenMarker is some private type, ensuring that such function can't be created outside syn.


  1. Well, except for the reexport for Ident, which is probably some quirk in how rustdoc treats reexports of the same name from different namespaces in general. ↩ī¸Ž

6 Likes

Thanks a lot. This trick is so interesting. :grinning: