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

How to use variable outside the macro #101

Open denizs opened 6 years ago

denizs commented 6 years ago

Hey there, I was just wondering, whether there was a way to declare a static ref based on a variable outside the macro. More specifically, something along the lines of

let sample = "something";

lazy_static! {
    pub static ref: MyStruct = MyStruct::new(sample);
}

Thanks in advance!

Pauan commented 6 years ago

I just ran into this problem as well. I would like to do the following:

fn initialize(options: Options, window: Window) {
    lazy_static! {
        static ref STATE: State = State::new(options, window);
    }

    ...
}

However I get this error:

error[E0434]: can't capture dynamic environment in a fn item
    --> src\bin\sidebar\main.rs:1386:46
     |
1386 |         static ref STATE: State = State::new(options, window);
     |                                              ^^^^^^^
     |
     = help: use the `|| { ... }` closure form instead

There are some complex reasons for why I need to use lazy_static (I can't just use a state local variable).

Pauan commented 6 years ago

I tried to implement this, and as far as I can tell it's currently impossible, due to static restrictions in Rust.

dtolnay commented 6 years ago

I wouldn't say this is static restrictions -- you will first need to figure out what stops someone from writing code like the following. After you conceptually understand what the behavior should be in such cases, it will be clearer how to proceed in implementing that behavior.

#[macro_use]
extern crate lazy_static;

fn initialize(options: Options, window: Window) -> &'static impl Deref<Target = State> {
    lazy_static! {
        static ref STATE: State = State::new(options, window);
    }

    &STATE
}

fn main() {
    let state = initialize(...);

    // Dereference STATE for the very first time, lazily initializing
    // it. But oops, by now `options` and `window` have been destroyed
    // so this is a use-after-free.
    println!("{:?}", **state);
}
Pauan commented 6 years ago

@dtolnay That particular issue isn't a problem, since the implementation I created uses a move closure to capture the outer variables.

The real issue is that it's not possible to put closures into static (which is why I said that the current static restrictions prevent this).

dtolnay commented 6 years ago

It's possible but unstable:

#![feature(existential_type, untagged_unions)]

#[macro_use]
extern crate lazy_static;

#[derive(Debug)]
struct Options(i32);
#[derive(Debug)]
struct Window(i32);
#[derive(Debug)]
struct State(Options, Window);

impl State {
    fn new(options: Options, window: Window) -> Self {
        State(options, window)
    }
}

existential type Init: FnOnce() -> State;

fn initialize(options: Options, window: Window) {
    #[allow(unions_with_drop_fields)]
    union BrieflyUninit {
        uninit: (),
        value: Init,
    }

    static mut INIT: BrieflyUninit = BrieflyUninit { uninit: () };
    let init = move || -> Init { move || State::new(options, window) };
    unsafe {
        std::ptr::write(&mut INIT.value, init());
    }

    lazy_static! {
        static ref STATE: State = unsafe { std::ptr::read(&INIT.value)() };
    }

    println!("{:?}", *STATE);
}

fn main() {
    initialize(Options(1), Window(2));
}