首页 > 解决方案 > 有没有办法在不使其成为静态 mut 的情况下初始化非平凡的静态 std::collections::HashMap ?

问题描述

在这段代码中,A不需要是static mut,但编译器强制Bstatic mut

use std::collections::HashMap;
use std::iter::FromIterator;

static A: [u32; 21] = [
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
];
static mut B: Option<HashMap<u32, String>> = None;

fn init_tables() {
    let hm = HashMap::<u32, String>::from_iter(A.iter().map(|&i| (i, (i + 10u32).to_string())));
    unsafe {
        B = Some(hm);
    }
}

fn main() {
    init_tables();
    println!("{:?} len: {}", A, A.len());
    unsafe {
        println!("{:?}", B);
    }
}

这是我发现接近我真正想要的东西的唯一方法:一个全局的、不可变HashMap的,可供多个函数使用,而不会用unsafe块乱扔我的所有代码。

我知道全局变量对于多线程应用程序来说是个坏主意,但我的是单线程的,那么我为什么要为永远不会发生的可能性付出代价呢?

由于我rustc直接使用而不是使用cargo,所以我不希望像lazy_static. 我试图破译该包中的宏的作用,但没有结束。

我也尝试用thread_local()和 a来写这个,RefCell但我在用那个版本A初始化时遇到了麻烦。B

更笼统地说,问题可能是“如何将东西放入 Rust 程序的 initvars 部分?”

如果你能告诉我如何B直接初始化(没有类似的函数init_tables()),你的答案可能是正确的。

如果像这样的函数init_tables()是不可避免的,是否有像访问函数这样的技巧来减少unsafe程序中的垃圾?

标签: rustglobal-variables

解决方案


如何将东西放入 Rust 程序的 initvars 部分?

结果rustcstatic数据放在生成的二进制文件的.rodata部分和static mut数据中:.data

#[no_mangle]
static DATA: std::ops::Range<u32> = 0..20;

fn main() { DATA.len(); }
$ rustc static.rs
$ objdump -t -j .rodata static
static:     file format elf64-x86-64

SYMBOL TABLE:
0000000000025000 l    d  .rodata    0000000000000000              .rodata
0000000000025490 l     O .rodata    0000000000000039              str.0
0000000000026a70 l     O .rodata    0000000000000400              elf_crc32.crc32_table
0000000000026870 l     O .rodata    0000000000000200              elf_zlib_default_dist_table
0000000000026590 l     O .rodata    00000000000002e0              elf_zlib_default_table
0000000000025060 g     O .rodata    0000000000000008              DATA
0000000000027f2c g     O .rodata    0000000000000100              _ZN4core3str15UTF8_CHAR_WIDTH17h6f9f810be98aa5f2E

因此,在源代码级别更改 fromstatic mut会显着更改生成的二进制文件。static.rodata部分是只读的,尝试写入它会使程序出现段错误。

如果 init_tables() 是审判日类别(不可避免)

这可能是不可避免的。由于默认.rodata链接不起作用,因此必须直接控制它:

use std::collections::HashMap;
use std::iter::FromIterator;

static A: std::ops::Range<u32> = 0..20;
#[link_section = ".bss"]
static B: Option<HashMap<u32, String>> = None;

fn init_tables() {
    let data = HashMap::from_iter(A.clone().map(|i| (i, (i + 10).to_string())));
    unsafe {
        let b: *mut Option<HashMap<u32, String>> = &B as *const _ as *mut _;
        (&mut *b).replace(data);
    }
}

fn main() {
    init_tables();
    println!("{:?} len: {}", A, A.len());
    println!("{:#?} 5 => {:?}", B, B.as_ref().unwrap().get(&5));
}

我不想要像lazy_static这样的外部板条箱的“帮助”

其实lazy_static没那么复杂。它巧妙地利用了这个Deref特性。这是一个非常简化的独立版本,它比第一个示例更符合人体工程学:

use std::collections::HashMap;
use std::iter::FromIterator;
use std::ops::Deref;
use std::sync::Once;

static A: std::ops::Range<u32> = 0..20;
static B: BImpl = BImpl;
struct BImpl;
impl Deref for BImpl {
    type Target = HashMap<u32, String>;

    #[inline(always)]
    fn deref(&self) -> &Self::Target {
        static LAZY: (Option<HashMap<u32, String>>, Once) = (None, Once::new());
        LAZY.1.call_once(|| unsafe {
            let x: *mut Option<Self::Target> = &LAZY.0 as *const _ as *mut _;
            (&mut *x).replace(init_tables());
        });

        LAZY.0.as_ref().unwrap()
    }
}

fn init_tables() -> HashMap<u32, String> {
    HashMap::from_iter(A.clone().map(|i| (i, (i + 10).to_string())))
}

fn main() {
    println!("{:?} len: {}", A, A.len());
    println!("{:#?} 5 => {:?}", *B, B.get(&5));
}

推荐阅读