Crates.io | poly-once |
lib.rs | poly-once |
version | 1.1.0 |
created_at | 2025-04-20 01:04:17.589859+00 |
updated_at | 2025-09-08 03:55:30.963654+00 |
description | A thread-safe cell providing async and sync initialization primitives similar to OnceLock |
homepage | |
repository | https://github.com/can1357/poly-once |
max_upload_size | |
id | 1641267 |
size | 132,606 |
A thread-safe cell providing initialization primitives similar to std::sync::OnceLock
but with a lock model that works with both sync and async code.
poly-once
provides two types for safe, one-time initialization of values:
Once<T>
: Basic one-time initialization cellTOnce<P, T>
: Parameterized initialization cell that transforms a parameter P
into value T
Both types ensure that initialization logic runs only once, even under concurrent access from multiple threads or async tasks. They leverage parking_lot_core
for efficient blocking synchronization.
get_or_init
, get_or_try_init
).async fn
or Future
s (get_or_init_async
, get_or_try_init_async
).Result
.parking_lot_core
for low-overhead synchronization when blocking is necessary.no_std
Compatibility: Can be used in no_std
environments (requires disabling default features if tokio
dependency is not desired).Add poly-once
to your Cargo.toml
:
[dependencies]
poly-once = "1" # Use the latest version
use poly_once::Once;
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
static DATA: Once<String> = Once::new();
fn get_data() -> &'static str {
DATA.get_or_init(|| {
// This closure runs only once
COUNTER.fetch_add(1, Ordering::Relaxed);
println!("Initializing data...");
// Simulate work
std::thread::sleep(std::time::Duration::from_millis(50));
"Expensive data".to_string()
})
}
fn main() {
let threads: Vec<_> = (0..5).map(|_| {
std::thread::spawn(|| {
println!("Thread access: {}", get_data());
})
}).collect();
for t in threads {
t.join().unwrap();
}
assert_eq!(DATA.get(), Some(&"Expensive data".to_string()));
assert_eq!(COUNTER.load(Ordering::Relaxed), 1); // Initializer ran only once
println!("Final data: {}", get_data());
}
use poly_once::Once;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::time::{sleep, Duration};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
static ASYNC_DATA: Once<String> = Once::new();
async fn get_async_data() -> &'static String {
ASYNC_DATA.get_or_init_async(async {
// This async block runs only once
COUNTER.fetch_add(1, Ordering::Relaxed);
println!("Initializing async data...");
sleep(Duration::from_millis(50)).await;
"Async expensive data".to_string()
}).await
}
#[tokio::main]
async fn main() {
let tasks: Vec<_> = (0..5).map(|_| {
tokio::spawn(async {
println!("Task access: {}", get_async_data().await);
})
}).collect();
for t in tasks {
t.await.unwrap();
}
assert_eq!(ASYNC_DATA.get(), Some(&"Async expensive data".to_string()));
assert_eq!(COUNTER.load(Ordering::Relaxed), 1); // Initializer ran only once
println!("Final async data: {}", get_async_data().await);
}
Handles cases where initialization might fail.
use poly_once::Once;
static MAYBE_DATA: Once<String> = Once::new();
fn try_get_data(fail: bool) -> Result<&'static String, &'static str> {
MAYBE_DATA.get_or_try_init(|| {
println!("Attempting initialization (fail={})...", fail);
if fail {
Err("Initialization failed!")
} else {
Ok("Successfully initialized".to_string())
}
})
}
fn main() {
// First attempt fails
match try_get_data(true) {
Ok(_) => panic!("Should have failed"),
Err(e) => println!("Caught error: {}", e),
}
assert!(!MAYBE_DATA.is_done()); // Still uninitialized
// Second attempt succeeds
match try_get_data(false) {
Ok(data) => println!("Got data: {}", data),
Err(_) => panic!("Should have succeeded"),
}
assert!(MAYBE_DATA.is_done());
assert_eq!(MAYBE_DATA.get(), Some(&"Successfully initialized".to_string()));
// Subsequent attempts (even failing ones) return the initialized value
match try_get_data(true) {
Ok(data) => println!("Got data again: {}", data),
Err(_) => panic!("Should have returned existing data"),
}
}
TOnce<P, T>
allows you to store a parameter value that will be transformed into the final value on first access.
use poly_once::TOnce;
// Store configuration that will be used to create a connection
static CONNECTION: TOnce<String, Connection> = TOnce::new("localhost:8080".to_string());
struct Connection {
addr: String,
}
impl Connection {
fn new(addr: String) -> Self {
println!("Establishing connection to {}", addr);
Connection { addr }
}
}
fn get_connection() -> &'static Connection {
CONNECTION.get_or_init(|addr| Connection::new(addr))
}
fn main() {
// First access creates the connection
let conn1 = get_connection();
println!("Using connection: {}", conn1.addr);
// Subsequent accesses return the same connection
let conn2 = get_connection();
assert!(std::ptr::eq(conn1, conn2));
}
use poly_once::TOnce;
use tokio::time::{sleep, Duration};
static ASYNC_CONFIG: TOnce<String, Config> = TOnce::new("config.json".to_string());
struct Config {
data: String,
}
async fn load_config(path: &str) -> Config {
println!("Loading config from {}", path);
sleep(Duration::from_millis(100)).await;
Config { data: format!("Config from {}", path) }
}
async fn get_config() -> &'static Config {
ASYNC_CONFIG.get_or_init_async(|path| load_config(path)).await
}
#[tokio::main]
async fn main() {
// Multiple concurrent tasks will only load the config once
let tasks: Vec<_> = (0..3).map(|i| {
tokio::spawn(async move {
let config = get_config().await;
println!("Task {} got config: {}", i, config.data);
})
}).collect();
for task in tasks {
task.await.unwrap();
}
}
poly-once
uses feature flags to enable asynchronous support and configure dependencies.
async-tokio
:
tokio
runtime, dropping the requirement of tokio::task::block_in_place
in exchange for a less efficient implementation.async-tokio-mt
:
tokio
runtime. Requires tokio
with the rt
and rt-multi-thread
features.no_std
Usage:
To use poly-once
in a no_std
environment without requiring tokio
, disable the default features:
[dependencies]
poly-once = { version = "1", default-features = false }
This will provide less efficient asyncronous methods without the support of the runtime.
Licensed under the MIT License. See the LICENSE file for details.
Contributions are welcome! Please feel free to submit pull requests or open issues on the repository.