Why does serde's derivation of Serialize/Deserialize produce code in const _: () block?

75 Views Asked by At

Note: this question isn't specifically about serde: it is about procedural macros and const function.

I'm studying the code produced by serde's derive macros for Serialize and Deserialize, and I haven't figured out why all of its code is inside a const _: ().

For example,

#[derive(Deserialize)]
struct MyNewType(i32);

expands to (using cargo expand):

#[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const _: () = {
    #[allow(unused_extern_crates, clippy::useless_attribute)]
    extern crate serde as _serde;
    #[automatically_derived]
    impl<'de> _serde::Deserialize<'de> for MyNewType {
        fn deserialize<__D>(
            __deserializer: __D,
        ) -> _serde::__private::Result<Self, __D::Error>
        where
            __D: _serde::Deserializer<'de>,
        {
         // generated implementation omitted for brevity
        }
    }
};

I discovered that if I carefully copy the stuff inside the const _: () = {...} block and paste it back in my source code after removing the #[derive(Deserialize)] tag, it all seems to compile and the deserialization continues to work just fine. So what purpose does putting everything in the const _: () block serve in my program?

1

There are 1 best solutions below

1
Silvio Mayolo On

It prevents the introduction of new names into the outer scope. When you copy-paste into your own scope, you're copying this line as well.

#[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate serde as _serde;

This introduces a name called _serde into the current scope. That could potentially conflict with your own code if you don't know to look for it. And worse, if multiple Deserialize macros expand in the same scope, then they'll both introduce the same name, which is a problem.

const _: () = {
  ...
}

So we run a block to get a new local scope that won't interfere with anything. The block returns (), which we neglect to bind. I'm not entirely sure why they needed a variable at all (instead of just the curly braces), though I suspect it may be to suppress unused variable warnings or prevent dead code elimination.