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?
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.
This introduces a name called
_serdeinto the current scope. That could potentially conflict with your own code if you don't know to look for it. And worse, if multipleDeserializemacros expand in the same scope, then they'll both introduce the same name, which is a problem.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.