rust-lang-nursery / lazy-static.rs

A small macro for defining lazy evaluated static variables in Rust.
Apache License 2.0
1.91k stars 111 forks source link

Using a lazy_static in its own initializer doesn't result in an error #95

Open luser opened 6 years ago

luser commented 6 years ago

I refactored some code I had that was creating a HashSet from a const slice to use lazy_static instead. I renamed the const and used its original name for the static, and then copy/pasted the initialization and forgot to rename the variable it was using, so the static wound up trying to initialize by iterating over itself. Surprisingly (to me), the code compiled anyway!

Here's a simple reduced testcase: https://play.rust-lang.org/?gist=d77058ab6da1a4e7815fab94305f2092&version=stable

Running this will deadlock and time out on the Playground.

I realize that this is probably hard to fix within the constraints of what Rust macros can do, but it's pretty unfortunate!

Kimundi commented 6 years ago

Yeah, that's a unfortunate error scenario - I'm not sure if we can do anything about it.

Maybe an extra boolean flag for a recursion check during initialization, but even for debug mode I'm not sure if thats to heavy-handed.

eddyb commented 6 years ago

Is this sound? Is Once reentrance-safe and blocking in this case, guaranteeing a deadlock? EDIT: looks like it! https://doc.rust-lang.org/std/sync/struct.Once.html#method.call_once

tuzz commented 3 years ago

Hello, I think I ran into the same problem with this example:

lazy_static::lazy_static! {
    static ref FOO: [usize; 2] = [0, FOO[0]];
}

fn main() {
    println!("{}", FOO[0]);
}

The code compiles fine but hangs.

eddyb commented 3 years ago

The macro could shadow the name with another definition, but I can't think of any way to cause a compiler error only on use. At most it could do something like this:

struct RecursiveUseWithinInitialierOfFOO;
const FOO: RecursiveUseWithinInitialierOfFOO = RecursiveUseWithinInitialierOfFOO;

And then the type errors might be a decent enough hint.

However, there's not really any way I can think of to stop code outside of the initializer expression, from using it, and getting called from the initializer, other than some kind of attribute that makes the compiler warn if the <typeof(FOO) as Deref>::deref method can ever call itself (that looks through the entire callgraph involving Once::call_once and whatnot - a bit like the "unconditional recursion" lint, but more powerful).