Hi guys, I am coming from a strong Node.JS async background (async/await + Promises), and trying to learn async Rust with Tokio.
I am able to get stuff to "work", but as part of coming to Rust I want to try and be as efficient as possible. My program needs to load up a config at startup (which can be considered "read-only" for the rest of the lifetime of the program). This then needs to be passed to handlers (which are tokio::spawned) for some use.
A very basic example program could be:
use tokio::{io::AsyncWriteExt, net::TcpListener};
#[derive(Clone)]
struct Config {
data: String,
}
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
// Assume config is initialized / read when program starts
// e.g. from CLI argument or config file
let cfg = Config {
data: "HELLO".to_string()
};
loop {
let (mut socket, _) = listener.accept().await.unwrap();
let cfg_copy = cfg.clone();
tokio::spawn(async move {
// assume we need to spawn for some long running I/O task
// but we need to use config (for READ ONLY) in this
socket.write(cfg_copy.data.as_bytes()).await;
});
}
}
I wanted to know, is there a better way to pass Config down to the guys who need it? I am guessing using .clone() comes with some extra memory use?
If I wrap it in an Arc and clone that, does it make any difference? From what I can tell, I must use async move to pass the socket inside.
Thanks for the suggestion. In my use case, since I know that the config must exist at the program start, and then it must be used (i.e. be "available") for the rest of the program, Box::leak seems like the simplest solution, since it guarantees that it will be readable from the ::spawn, and only use memory once. Very useful!
OnceLock and LazyLock look interesting, but I wonder if they are overkill in this scenario, where I know the initialization must happen in main before doing anything else (i.e. I don't need thread-safe initialization).
Also thanks for informing me on the Arc cloning, will definitely keep that in mind if I ever need something which can't be leaked out, since it seems cheaper than cloning the whole object (which could get quite big).
For reference to anyone searching, this is how I leaked the config:
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
// Assume config is initialized / read when program starts
// e.g. from CLI argument or config file
let cfg = Box::new(Config {
data: "HELLO".to_string()
});
let leaked: &'static Config = Box::leak(cfg);
loop {
let (mut socket, _) = listener.accept().await.unwrap();
tokio::spawn(async move {
// assume we need to spawn for some long running I/O task
// but we need to use config (for READ ONLY) in this
socket.write(&leaked.data.as_bytes()).await;
});
}
}