18

I'm trying to load passwords and sensitive data from the system's environment when my service starts up. I've tried a number of different ways but can't seem to figure out the right way to do this in Rust.

const PASSWORD: String = var("PASSWORD").unwrap();

Doesn't work because method calls in constants are limited to constant inherent methods. The same applies to static (except the error says statics, obviously).

The other way I've seen to do it is

const PASSWORD: &'static str = env!("PASSWORD");

But the problem with that is it will be defined at compile time as env! is a macro (at least that is my understanding).

I also considered simply wrapping the call to var("...").unwrap() in a function but that solution doesn't really sit right with me, and would also allow the values to change throughout runtime AND not validate them when the service starts.

As you can tell I'm new to Rust. I'd really appreciate if in your answer you could not just explain how to load the const/static at runtime but also explain why what I'm doing is dumb and wrong :)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
dave
  • 1,127
  • 2
  • 10
  • 26

1 Answers1

20

const and static fill different roles in Rust.

const does not only mean a constant, it means a compile-time constant, a value determined at compile-time and inscribed in the read-only memory of the program. It is not suitable for your usecase.

static means a global variable, with a lifetime that will span the entire program. It may be mutable, in which case it must be Sync to avoid concurrent modifications. A static variable must be initialized from a constant, in order to be available from the start of the program.

So, how to read a variable at run-time and have it available? Well, a clean solution would be to avoid globals altogether...

... but since it can be convenient, there is a crate for it: lazy_static!.

use std::env::var;
use lazy_static::lazy_static;

lazy_static! {
    static ref PASSWORD: String = var("PASSWORD").unwrap();
}

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

On first access to the variable, the expression is executed to load its value, the value is then memoized and available until the end of the program.

Sean Allred
  • 3,558
  • 3
  • 32
  • 71
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Is there a better way? I'd prefer the variables to be loaded and have their presence validated at the time the service starts rather than when they are first requested. But if you don't like globals how else would you handle environment variables? Assuming they're not to change after the service starts would there be somewhere better I could put them? – dave May 24 '16 at 07:44
  • 4
    @dave: that's how globals are handled in Rust, just access them at the first opportunity (in main) and they will be loaded at the earliest possible point. As for not using globals, why not simply retrieve the password in a variable and then pass it (via arguments) to whoever needs it? Same functionality, but it makes it explicit which block of code accesses the password. – Matthieu M. May 24 '16 at 07:48
  • Haha, almost too simple. But yeah I should probably just do it that way and validate their presence while I load them. Thanks for the shot of clarity. – dave May 24 '16 at 07:56
  • `lazy_static` crate will lead to error `multiple matching crates for `lazy_static``. – xxks-kkk Sep 02 '18 at 17:28
  • 1
    I needed `ref`: `static ref PASSWORD: String = var("PASSWORD").unwrap();` – Rokit Oct 24 '19 at 21:57
  • 2
    @Rokit: Indeed. I also updated the snippet to provide a [MVCE], if we ask for it in questions, no reasons not to also provide it in answers :) – Matthieu M. Oct 25 '19 at 06:55
  • such a nice answer – Alex Vergara Sep 05 '22 at 17:33
  • @MatthieuM. I am noticing this example does not compile for me, saying `PASSWORD` does not implement `Debug`. I can only get to compile with: `let r: &str = &PASSWORD; println!("{r:?}");` – ecoe Apr 22 '23 at 16:25
  • @ecoe: That's strange, it compiles for me on the playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=759e003cb4e7225276064823e38cce06 . Can you reproduce your issue there? – Matthieu M. Apr 22 '23 at 18:24
  • I am on an ARM 64 processor, sorry I don't see how to set that in the playground or if it matters... – ecoe Apr 22 '23 at 19:32
  • @ecoe: The type of processor shouldn't matter with regard to such a compilation error; I encourage you to ask a question of your own, with a full reproducible example and the exact text of the error. – Matthieu M. Apr 23 '23 at 10:42
  • Thanks, I'll try to put together a minimal reproducible answer. The only other difference is that I'm using tokio for asynchronous functions that call the `println!` – ecoe Apr 23 '23 at 15:28