在Rust中,在运行时使用环境变量填充静态/常量

19

我正在尝试在我的服务启动时从系统环境中加载密码和敏感数据。我尝试了许多不同的方法,但好像无法找到在Rust中正确的方法。

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

无法工作,因为常量中的方法调用仅限于常量固有方法。这同样适用于静态方法(除了错误明显说了静态)。

我看到的另一种方法是

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

但问题在于,因为env!是一个宏(至少我是这样理解的),所以它将在编译时被定义。

我也考虑过简单地将对var("...").unwrap()的调用封装在一个函数中,但这个解决方案并不完全符合我的要求,而且还允许值在运行时更改,并且不会在服务启动时进行验证。

正如您所看到的,我对Rust还很陌生。如果您能在回答中不仅解释如何在运行时加载const/static,而且还解释我正在做的事情有什么错误和愚蠢之处,我将不胜感激 :)

2个回答

21

conststatic在Rust中扮演不同的角色。

const不仅表示常量,还表示一个在编译时确定并在程序的只读内存中记录下来的常量。它不适合您的用例。

static表示一个全局变量,其生命周期将跨越整个程序。它可以是可变的,在这种情况下,它必须是Sync以避免并发修改。为了从程序开始就可用,static变量必须从一个常量初始化。

那么,如何在运行时读取变量并使其可用?嗯,一个干净的解决方案是完全避免全局变量...

...但既然它可能很方便,所以有一个crate: lazy_static!.

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

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

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

第一次访问该变量时,表达式将被执行以加载其值。然后将其缓存并保留到程序结束。


4
@dave:这就是 Rust 处理全局变量的方式,只需在第一次机会(在 main 函数中)访问它们,它们就会在最早的时间点加载。至于不使用全局变量,为什么不直接将密码检索到一个变量中,然后通过参数传递给需要它的人呢?功能相同,但它可以明确哪个代码块访问了密码。 - Matthieu M.
哈哈,几乎太简单了。但是是的,我应该这样做,并在加载它们时验证它们的存在。感谢你提供清晰的思路。 - dave
1
我需要 refstatic ref PASSWORD: String = var("PASSWORD").unwrap(); - Rokit
2
@Rokit:确实。我还更新了代码片段,以提供[最小可复现例],如果我们在问题中要求它,在回答中也没有理由不提供 :) - Matthieu M.
这是一个非常好的答案。 - Alex Vergara
显示剩余7条评论

3
现在用OnceLock代替lazy_static是正确的,但Matthieu M.说的一切仍然正确。
use std::sync::OnceLock;
use std::env::var;
fn password() -> &'static str {
    static PASSWORD: OnceLock<String> = OnceLock::new();
    PASSWORD.get_or_init(|| {
        var("PASSWORD").unwrap()
    })
}

一旦它稳定或在夜间版本上,你可以简化为一个LazyLock
#![feature(lazy_cell)]
use std::sync::LazyLock;
use std::env::var;
static PASSWORD: LazyLock<String> = LazyLock::new(|| {
    var("PASSWORD").unwrap()
});

你还可以通过使用Box<str>代替String并在字符串上调用.into()来节省每个变量的一个字(根据String的实际分配情况,可能还可以节省更多),但这可能会涉及额外的字符串内容复制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接