Rust单次初始化映射库once_map的使用,提供线程安全且高效的一次性键值存储解决方案

Rust单次初始化映射库once_map的使用,提供线程安全且高效的一次性键值存储解决方案

Crates.io Docs.rs Minimum rustc version

这个crate提供了OnceMap,一种HashMap类型,其中条目可以通过共享引用写入,但只能写入一次。这类似于once_cell,但带有映射功能。这使得可以在映射的生命周期内引用映射中的值,而无需进一步的锁。

这种类型非常适合实现缓存。为此提供了LazyMap类型。

这个crate提供了一个为并发使用而高度优化的映射,但也提供了单线程版本。

示例

let map = OnceMap::new();

// 所有这些都是指向映射内部的`&str`
// 注意我们不需要可变引用,所以我们可以同时拥有多个
let roses = map.insert(String::from("rose"), |_| String::from("red"));
let violets = map.insert(String::from("violets"), |_| String::from("blue"));
let sugar = map.insert(String::from("sugar"), |_| String::from("sweet"));

assert_eq!(roses, "red");
assert_eq!(violets, "blue");
assert_eq!(sugar, "sweet");

// 这里闭包永远不会运行,因为我们已经有了"rose"的值
let roses = map.insert(String::from("rose"), |_| String::from("green"));
// 旧值没有改变
assert_eq!(roses, "red");

完整示例代码

use once_map::OnceMap;

fn main() {
    // 创建一个新的OnceMap实例
    let map = OnceMap::new();

    // 插入键值对,每个键只能插入一次
    // 如果键已存在,则返回现有值,不会执行闭包
    let roses = map.insert(String::from("rose"), |_| {
        println!("Initializing rose color");
        String::from("red")
    });

    let violets = map.insert(String::from("violets"), |_| {
        println!("Initializing violets color");
        String::from("blue")
    });

    let sugar = map.insert(String::from("sugar"), |_| {
        println!("Initializing sugar taste");
        String::from("sweet")
    });

    // 验证插入的值
    assert_eq!(roses, "red");
    assert_eq!(violets, "blue");
    assert_eq!(sugar, "sweet");

    // 尝试再次插入相同的键
    // 闭包不会执行,返回已存在的值
    let roses_again = map.insert(String::from("rose"), |_| {
        println!("This should not be printed");
        String::from("green")
    });

    // 确认返回的是原始值
    assert_eq!(roses_again, "red");

    // 插入更多值
    let sky = map.insert(String::from("sky"), |_| {
        println!("Initializing sky color");
        String::from("blue")
    });

    let grass = map.insert(String::from("grass"), |_| {
        println!("Initializing grass color");
        String::from("green")
    });

    // 最终验证
    assert_eq!(sky, "blue");
    assert_eq!(grass, "green");

    println!("All values initialized successfully!");
}

安装

在项目目录中运行以下Cargo命令:

cargo add once_map

或者在Cargo.toml中添加以下行:

once_map = "0.4.21"

特性

  • 线程安全: 为并发使用而优化
  • 单次初始化: 每个键只能设置一次值
  • 高效: 避免不必要的锁和初始化
  • 无标准库支持: 支持no_std环境
  • 缓存友好: 适合实现各种缓存模式

使用场景

  • 全局配置存储
  • 单例模式实现
  • 延迟初始化缓存
  • 线程安全的资源池
  • 避免重复计算的缓存系统

许可证

MIT OR Apache-2.0


1 回复

Rust单次初始化映射库once_map使用指南

概述

once_map是一个专为Rust设计的线程安全、高性能的单次初始化键值存储库。它允许在并发环境中安全地初始化每个键对应的值一次,并在后续访问中高效地重用已初始化的值。

核心特性

  • 线程安全:基于Rust的Arc和Mutex实现线程安全
  • 惰性初始化:值只在第一次访问时初始化
  • 零成本抽象:编译时优化确保运行时高性能
  • 类型安全:充分利用Rust的类型系统保证安全性

安装方法

在Cargo.toml中添加依赖:

[dependencies]
once_map = "0.3"

基本用法示例

use once_map::OnceMap;
use std::sync::Arc;
use std::thread;

fn main() {
    let map = Arc::new(OnceMap::new());
    
    // 多线程环境下安全初始化
    let handles: Vec<_> = (0..10).map(|i| {
        let map_clone = Arc::clone(&map);
        thread::spawn(move || {
            let value = map_clone.get_or_init(&i, || {
                println!("初始化键 {} 的值", i);
                i * 2
            });
            assert_eq!(*value, i * 2);
        })
    }).collect();

    for handle in handles {
        handle.join().unwrap();
    }
    
    // 验证每个键只初始化了一次
    assert_eq!(map.len(), 10);
}

高级用法

1. 自定义初始化函数

use once_map::OnceMap;

struct ExpensiveResource {
    data: Vec<u8>,
}

impl ExpensiveResource {
    fn new(size: usize) -> Self {
        // 模拟昂贵的初始化操作
        println!("创建昂贵资源,大小: {}", size);
        Self {
            data: vec![0; size],
        }
    }
}

fn main() {
    let map = OnceMap::new();
    
    let resource = map.get_or_init(&"large_resource", || {
        ExpensiveResource::new(1024 * 1024) // 1MB资源
    });
    
    println!("资源大小: {}", resource.data.len());
}

2. 处理初始化失败

use once_map::OnceMap;
use std::io;

fn load_config(key: &str) -> Result<String, io::Error> {
    // 模拟可能失败的操作
    if key == "invalid" {
        Err(io::Error::new(io::ErrorKind::NotFound, "配置不存在"))
    } else {
        Ok(format!("{}_config", key))
    }
}

fn main() -> Result<(), io::Error> {
    let map = OnceMap::new();
    
    let config = map.get_or_try_init("app", || {
        load_config("app")
    })?;
    
    println!("加载的配置: {}", config);
    Ok(())
}

3. 与异步代码集成

use once_map::OnceMap;
use tokio::runtime::Runtime;

async fn fetch_data(key: &str) -> String {
    // 模拟异步网络请求
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    format!("{}_data", key)
}

fn main() {
    let map = OnceMap::new();
    let rt = Runtime::new().unwrap();
    
    let data = map.get_or_init(&"user", || {
        rt.block_on(fetch_data("user"))
    });
    
    println!("获取的数据: {}", data);
}

完整示例demo

use once_map::OnceMap;
use std::sync::Arc;
use std::thread;
use std::io;

// 示例1:基本多线程使用
fn basic_usage() {
    println!("=== 基本用法示例 ===");
    let map = Arc::new(OnceMap::new());
    
    let handles: Vec<_> = (0..5).map(|i| {
        let map_clone = Arc::clone(&map);
        thread::spawn(move || {
            let value = map_clone.get_or_init(&i, || {
                println!("线程 {} 初始化键 {}", thread::current().id(), i);
                i * i // 计算平方值
            });
            println!("线程 {} 获取值: {}", thread::current().id(), value);
        })
    }).collect();

    for handle in handles {
        handle.join().unwrap();
    }
    println!("映射大小: {}", map.len());
}

// 示例2:自定义复杂类型
fn custom_type_usage() {
    println!("\n=== 自定义类型示例 ===");
    #[derive(Debug)]
    struct DatabaseConnection {
        url: String,
        connected: bool,
    }
    
    impl DatabaseConnection {
        fn new(url: &str) -> Self {
            println!("建立数据库连接: {}", url);
            Self {
                url: url.to_string(),
                connected: true,
            }
        }
    }

    let map = OnceMap::new();
    
    // 获取或创建数据库连接
    let conn1 = map.get_or_init(&"primary_db", || {
        DatabaseConnection::new("postgres://localhost:5432/mydb")
    });
    
    let conn2 = map.get_or_init(&"primary_db", || {
        DatabaseConnection::new("这不会被调用,因为键已存在")
    });
    
    println!("连接1: {:?}", conn1);
    println!("连接2: {:?}", conn2); // 与conn1相同
}

// 示例3:错误处理
fn error_handling_usage() -> Result<(), io::Error> {
    println!("\n=== 错误处理示例 ===");
    
    fn load_secret(key: &str) -> Result<String, io::Error> {
        if key == "admin_token" {
            Ok("secret_admin_token_123".to_string())
        } else {
            Err(io::Error::new(io::ErrorKind::PermissionDenied, "无权限访问"))
        }
    }
    
    let map = OnceMap::new();
    
    // 成功案例
    let token = map.get_or_try_init("admin_token", || {
        load_secret("admin_token")
    })?;
    println!("获取到的token: {}", token);
    
    // 失败案例
    match map.get_or_try_init("user_token", || {
        load_secret("user_token")
    }) {
        Ok(_) => println!("这不会发生"),
        Err(e) => println!("预期错误: {}", e),
    }
    
    Ok(())
}

// 示例4:性能优化 - 使用Copy类型键
fn performance_optimization() {
    println!("\n=== 性能优化示例 ===");
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    enum CacheKey {
        UserProfile,
        Settings,
        Permissions,
    }
    
    let map = OnceMap::new();
    
    let profile = map.get_or_init(&CacheKey::UserProfile, || {
        println!("加载用户配置数据...");
        vec!["name", "email", "age"]
    });
    
    let settings = map.get_or_init(&CacheKey::Settings, || {
        println!("加载设置数据...");
        vec!["theme", "language", "notifications"]
    });
    
    println!("用户配置: {:?}", profile);
    println!("设置: {:?}", settings);
}

fn main() -> Result<(), io::Error> {
    // 运行所有示例
    basic_usage();
    custom_type_usage();
    error_handling_usage()?;
    performance_optimization();
    
    Ok(())
}

性能优化技巧

  1. 使用合适的键类型:优先使用Copy类型作为键
  2. 避免昂贵的克隆:在初始化闭包中避免不必要的克隆操作
  3. 批量初始化:对于已知需要初始化的键,可以预先初始化

注意事项

  • 初始化函数应该是幂等的
  • 避免在初始化函数中产生死锁
  • 对于非常大的数据集,考虑内存使用情况

替代方案比较

lazy_staticonce_cell相比,once_map提供了:

  • 更细粒度的控制(每个键单独初始化)
  • 更好的内存效率(只存储实际使用的键值对)
  • 更灵活的错误处理机制

这个库特别适合需要按需初始化大量资源的场景,如配置管理、资源缓存和延迟加载等应用。

回到顶部