Lazy_static: trying to understand this `impl <Trait> for <identitifer>`

I'm looking at this two lines of code as seen in lazy_static crate.

First line as follows

macro_rules! __lazy_static_internal {
    // optional visibility restrictions are wrapped in `()` to allow for
    // explicitly passing otherwise implicit information about private items
    ($(#[$attr:meta])* ($($vis:tt)*) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {    // <------ THIS LINE
        __lazy_static_internal!(@MAKE TY, $(#[$attr])*, ($($vis)*), $N);
        __lazy_static_internal!(@TAIL, $N : $T = $e);
        lazy_static!($($t)*);
    };

Second line as follows

(@TAIL, $N:ident : $T:ty = $e:expr) => {
        impl $crate::__Deref for $N {     // <---- THIS LINE
            type Target = $T;
            fn deref(&self) -> &$T {
                #[inline(always)]
                fn __static_ref_initialize() -> $T { $e }

                #[inline(always)]
                fn __stability() -> &'static $T {
                    __lazy_static_create!(LAZY, $T);
                    LAZY.get(__static_ref_initialize)
                }
                __stability()
            }
        }

I wonder does that mean the code says impl $crate::__Deref for $N in which $N is identifier. Shouldn't it be type? I'm not trying to invalidate the code, but I'm trying to prove my understanding of rust that impl should be doing against type, thus what follows for should be $T instead, and not identifier. I've tried on playground with a simple case against global static variable, and rust compiler expects a type. Anyway, lazy_static code is already in pure correct. So I believe I misread the code.

Any answer to clear this up would be very much appreciated.

1 Like

A type can be denoted by a single identifier. In fact all primitive types (i32, f64, bool) and user-
defined types currently in scope (e.g. struct and enum types) can be referred to using a single identifier.

You seem to be confusing "identifier" with "variable". Different identifiers refer to different kinds of entities. Some refer to variables, some to constants, some to types, others to functions, etc.

Nope. The Deref impl is for the wrapper which performs the lazy initialization. That's the very point of the macro. The result of the deref operation is &$T, but you can't just implement Deref blindly for the type of the static:

  • first of all, in that case, impls would conflict if you created multiple lazy_static!s with the same type.
  • In addition, it's not possible to impl Deref for types you don't own due to orphan rules.
  • Third, Deref might already be implemented for the type of your static, in which it would be a conflicting impl, too.
1 Like

So I finally think about checking expanded code.
I use the following command for 2021 edition (only in nightly).

cargo rustc -- -Zunpretty=expanded`

The original code using lazy_static is as follows

    lazy_static! {
        static ref LOCK: Mutex<i64> = Mutex::new(0);
    }

it expands into

        #[allow(missing_copy_implementations)]
        #[allow(non_camel_case_types)]
        #[allow(dead_code)]
        struct LOCK {
            __private_field: (),
        }
        #[doc(hidden)]
        static LOCK: LOCK = LOCK{__private_field: (),};
        impl ::lazy_static::__Deref for LOCK {
            type Target = Mutex<i64>;
            fn deref(&self) -> &Mutex<i64> {
                #[inline(always)]
                fn __static_ref_initialize() -> Mutex<i64> { Mutex::new(0) }
                #[inline(always)]
                fn __stability() -> &'static Mutex<i64> {
                    static LAZY: ::lazy_static::lazy::Lazy<Mutex<i64>> =
                        ::lazy_static::lazy::Lazy::INIT;
                    LAZY.get(__static_ref_initialize)
                }
                __stability()
            }
        }
        impl ::lazy_static::LazyStatic for LOCK {
            fn initialize(lazy: &Self) { let _ = &**lazy; }
        }

The code I referenced in my question is not the whole picture. My bad that I didn't pay attention much to the struct declaration at these lines.

Note: It use our identifier name to declare the same name as a struct i.e. LOCK.

I'm clear now.

Ok, thanks. I carefully recheck and come to terms with myself. I'll try using the right terms next time.

Thank you for pinpointing the niffy detail for me. Agree, it's wrong to use $T as I saw now. The whole thing is for that wrapper type, I misread the code.

Thanks again for your comment. I understood more now.

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.