matklad / once_cell

Rust library for single assignment cells and lazy statics without macros
Apache License 2.0
1.87k stars 109 forks source link

Is it possible to use sync::Lazy together with constants/static? #244

Closed REASY closed 1 year ago

REASY commented 1 year ago

Hello,

I'm trying to get the following code to compile, but didn't succeed.

/*
[dependencies]
once_cell = "1.18.0"

*/
use once_cell::sync::Lazy;

#[derive(Debug, Clone)]
pub struct Settings {
    pub environment: String,
    pub path: String,
    pub zoom: u32
}

impl Settings {
    pub fn new() -> Settings {
        Settings { environment: "dev".to_string(), path: "foo".to_string(), zoom: 1u32 }
    }
}

pub static SETTINGS: Lazy<Settings> = Lazy::new(|| {
    Settings::new()
});

static ZOOM: u32 = SETTINGS.zoom;

const CONST_ZOOM: u32 = SETTINGS.zoom;

fn main() {
    println!("{:?}", SETTINGS);
}

The errors:

error[E0015]: cannot perform deref coercion on `once_cell::sync::Lazy<Settings>` in statics
    --> src/main.rs:28:20
     |
28   | static ZOOM: u32 = SETTINGS.zoom;
     |                    ^^^^^^^^^^^^^
     |
     = note: attempting to deref into `Settings`
note: deref defined here

error[E0013]: constants cannot refer to statics
  --> src/main.rs:30:25
   |
30 | const CONST_ZOOM: u32 = SETTINGS.zoom;
   |                         ^^^^^^^^
   |
   = help: consider extracting the value of the `static` to a `const`, and referring to that

error[E0015]: cannot perform deref coercion on `once_cell::sync::Lazy<Settings>` in constants
    --> src/main.rs:30:25
     |
30   | const CONST_ZOOM: u32 = SETTINGS.zoom;
     |                         ^^^^^^^^^^^^^

Playground at [rustexplorer](https://www.rustexplorer.com/b#%2F*%0A%5Bdependencies%5D%0Aonce_cell%20%3D%20%221.18.0%22%0A%0A*%2F%0A%0Ause%20once_cell%3A%3Async%3A%3ALazy%3B%0A%0A%23%5Bderive(Debug%2C%20Clone)%5D%0Apub%20struct%20Settings%20%7B%0A%20%20%20%20pub%20environment%3A%20String%2C%0A%20%20%20%20pub%20path%3A%20String%2C%0A%20%20%20%20pub%20zoom%3A%20u32%0A%7D%0A%0A%0Aimpl%20Settings%20%7B%0A%20%20%20%20pub%20fn%20new()%20-%3E%20Settings%20%7B%0A%20%20%20%20%20%20%20%20Settings%20%7B%20environment%3A%20%22dev%22.to_string()%2C%20path%3A%20%22foo%22.to_string()%2C%20zoom%3A%201u32%20%7D%0A%20%20%20%20%7D%0A%7D%0A%0Apub%20static%20SETTINGS%3A%20Lazy%3CSettings%3E%20%3D%20Lazy%3A%3Anew(%7C%7C%20%7B%0A%20%20%20%20Settings%3A%3Anew()%0A%7D)%3B%0A%0A%0Astatic%20ZOOM%3A%20u32%20%3D%20SETTINGS.zoom%3B%0A%0Aconst%20CONST_ZOOM%3A%20u32%20%3D%20SETTINGS.zoom%3B%0A%0Afn%20main()%20%7B%0A%20%20%20%20println!(%22%7B%3A%3F%7D%22%2C%20SETTINGS)%3B%0A%7D)

Thank you.

NobodyXu commented 1 year ago

Lazy is initialized on acess, so ZOOM would also be Lazy<u32> and CONST_ZOOM is just not possible.

There is actually an alternative to using Lazy here, if you are willing to change Settings to:

use std::borrow::Cow;

#[derive(Debug, Clone)]
pub struct Settings {
    pub environment: Cow<'_, str>,
    pub path: Cow<'_, str>,
    pub zoom: u32
}

impl Settings {
    pub const fn new() -> Settings {
        Settings { environment: Cow::Borrowed("dev"), path: Cow::Borrowed("foo"), zoom: 1u32 }
    }
}

pub const SETTINGS: Settings = Settings::new();

static ZOOM: u32 = SETTINGS.zoom;

const CONST_ZOOM: u32 = SETTINGS.zoom;

Or you can replace String with CompactString, which has a const constructor CompactString::new_inline for strings that are no longer than 24 bytes on 64-bit platform and strings that are no longer than 12 bytes on 32-bit platform.

REASY commented 1 year ago

Hi, @NobodyXu, thank you the answer!

I cannot go with const SETTINGS because of my use-case, I'm loading settings from config file using config crate, so cannot be const, right?

This is the full example of what [I would want to achieve](https://www.rustexplorer.com/b#%2F*%0A%5Bdependencies%5D%0Aonce_cell%20%3D%20%221.18.0%22%0Aserde%20%3D%20%7B%20version%20%3D%20%221.0%22%2C%20features%20%3D%20%5B%22derive%22%5D%20%7D%0Aserde_json%20%3D%20%221.0%22%0Aconfig%20%3D%20%220.13%22%0A%0A*%2F%0A%0Ause%20config%3A%3A%7BConfig%2C%20ConfigError%2C%20Environment%2C%20File%7D%3B%0Ause%20once_cell%3A%3Async%3A%3ALazy%3B%0Ause%20serde%3A%3ADeserialize%3B%0Ause%20std%3A%3A%7Benv%2C%20fmt%7D%3B%0Ause%20std%3A%3Aborrow%3A%3ACow%3B%0A%0A%0A%23%5Bderive(Debug%2C%20Clone%2C%20Deserialize)%5D%0Apub%20struct%20Server%3C'a%3E%20%7B%0A%20%20%20%20pub%20address%3A%20Cow%3C'a%2C%20str%3E%2C%0A%20%20%20%20pub%20port%3A%20u16%2C%0A%7D%0A%0A%23%5Bderive(Debug%2C%20Clone%2C%20Deserialize)%5D%0Apub%20struct%20Logger%3C'a%3E%20%7B%0A%20%20%20%20pub%20level%3A%20Cow%3C'a%2C%20str%3E%2C%0A%7D%0A%0A%23%5Bderive(Debug%2C%20Clone%2C%20Deserialize)%5D%0Apub%20struct%20TileStore%3C'a%3E%20%7B%0A%20%20%20%20pub%20base_path%3A%20Cow%3C'a%2C%20str%3E%2C%0A%20%20%20%20pub%20zoom_level%3A%20u32%2C%0A%7D%0A%0A%23%5Bderive(Debug%2C%20Clone%2C%20Deserialize)%5D%0Apub%20struct%20Settings%3C'a%3E%20%7B%0A%20%20%20%20pub%20environment%3A%20Cow%3C'a%2C%20str%3E%2C%0A%20%20%20%20pub%20server%3A%20Server%3C'a%3E%2C%0A%20%20%20%20pub%20logger%3A%20Logger%3C'a%3E%2C%0A%20%20%20%20pub%20tile_store%3A%20TileStore%3C'a%3E%2C%0A%7D%0A%0Aimpl%20Settings%3C'_%3E%20%7B%0A%20%20%20%20pub%20fn%20new()%20-%3E%20Result%3CSelf%2C%20ConfigError%3E%20%7B%0A%20%20%20%20%20%20%20%20let%20run_mode%20%3D%20env%3A%3Avar(%22RUN_MODE%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.map(%7Cx%7C%20x.to_lowercase())%0A%20%20%20%20%20%20%20%20%20%20%20%20.unwrap_or_else(%7C_%7C%20%22dev%22.into())%3B%0A%0A%20%20%20%20%20%20%20%20let%20mut%20builder%20%3D%20Config%3A%3Abuilder()%0A%20%20%20%20%20%20%20%20%20%20%20%20.add_source(File%3A%3Awith_name(%22config%2Fdefault%22).required(false))%0A%20%20%20%20%20%20%20%20%20%20%20%20.add_source(File%3A%3Awith_name(%26format!(%22config%2F%7Brun_mode%7D%22)).required(false))%0A%20%20%20%20%20%20%20%20%20%20%20%20.add_source(File%3A%3Awith_name(%22config%2Flocal%22).required(false))%0A%20%20%20%20%20%20%20%20%20%20%20%20.add_source(Environment%3A%3Adefault().separator(%22__%22))%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Some%20cloud%20services%20like%20Heroku%20exposes%20a%20randomly%20assigned%20port%20in%0A%20%20%20%20%20%20%20%20%2F%2F%20the%20PORT%20env%20var%20and%20there%20is%20no%20way%20to%20change%20the%20env%20var%20name.%0A%20%20%20%20%20%20%20%20if%20let%20Ok(port)%20%3D%20env%3A%3Avar(%22PORT%22)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20builder%20%3D%20builder.set_override(%22server.port%22%2C%20port)%3F%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20builder%0A%20%20%20%20%20%20%20%20%20%20%20%20.build()%3F%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Deserialize%20(and%20thus%20freeze)%20the%20entire%20configuration.%0A%20%20%20%20%20%20%20%20%20%20%20%20.try_deserialize()%0A%20%20%20%20%7D%0A%7D%0A%0Aimpl%20fmt%3A%3ADisplay%20for%20Server%3C'_%3E%20%7B%0A%20%20%20%20fn%20fmt(%26self%2C%20f%3A%20%26mut%20fmt%3A%3AFormatter)%20-%3E%20fmt%3A%3AResult%20%7B%0A%20%20%20%20%20%20%20%20write!(f%2C%20%22http%3A%2F%2F%7B%7D%3A%7B%7D%22%2C%20%26self.address%2C%20%26self.port)%0A%20%20%20%20%7D%0A%7D%0A%0Apub%20static%20SETTINGS%3A%20Lazy%3CSettings%3E%20%3D%0A%20%20%20%20Lazy%3A%3Anew(%7C%7C%20Settings%3A%3Anew().expect(%22Failed%20to%20setup%20settings%22))%3B%0A%0Afn%20get_gdal_vsi_path(base_path%3A%20%26str)%20-%3E%20String%20%7B%0A%20%20%20%20if%20base_path.starts_with(%22s3%3A%2F%2F%22)%20%7B%0A%20%20%20%20%20%20%20%20base_path.replace(%22s3%3A%2F%2F%22%2C%20%22%2Fvsis3%2F%22)%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20String%3A%3Afrom(base_path)%0A%20%20%20%20%7D%0A%7D%0A%0A%2F%2F%20Does%20not%20compile.%20I%20want%20this%20to%20be%20initialized%20only%20once%20instead%20of%20local%20variable%20in%20main%0Astatic%20GDAL_VSI_BASE_PATH%3A%20String%20%3D%20get_gdal_vsi_path(%26SETTINGS.tile_store.base_path)%3B%0A%0A%0Afn%20main()%20%7B%0A%20%20%20%20let%20base_path%3A%20String%20%3D%20get_gdal_vsi_path(%26SETTINGS.tile_store.base_path)%3B%0A%0A%20%20%20%20println!(%22%7B%7D%22%2C%20base_path)%3B%0A%7D), compiler errors with:

error[E0015]: cannot perform deref coercion on `once_cell::sync::Lazy<Settings<'_>>` in statics
    --> src/main.rs:85:56
error[E0015]: cannot perform deref coercion on `Cow<'_, str>` in statics
  --> src/main.rs:85:55
error[E0015]: cannot call non-const fn `get_gdal_vsi_path` in statics
  --> src/main.rs:85:37

Compiler is quite straightforward in the last message, "cannot call non-const fnget_gdal_vsi_pathin statics. So there is no way to achieve what I want, having static variable initialized only once instead of creating it in the method always?

Thanks.

NobodyXu commented 1 year ago

I cannot go with const SETTINGS because of my use-case, I'm loading settings from config file using config crate, so cannot be const, right?

In that case, yes, you would have to use OnceCell or arc-swap.

In your example:

// Does not compile. I want this to be initialized only once instead of local variable in main
static GDAL_VSI_BASE_PATH: Lazy<String> = Lazy::new(|| get_gdal_vsi_path(&SETTINGS.tile_store.base_path));

Also, OnceCell::get_or_try_init allow you to call fallible function and return error instead of having to panic.

REASY commented 1 year ago

Thank you, @NobodyXu !

I assume the runtime overhead of the type being lazy is negligible, the case of OnceCell lazy there is nothing heavy in force method?

NobodyXu commented 1 year ago

Thank you, @NobodyXu !

You are welcome

I assume the runtime overhead of the type being lazy is negligible, the case of OnceCell lazy there is nothing heavy in force method?

No, it's just the same as OnceCell.

It's generally quite cheap.