I am trying to move values into a ctrlc
closure, but update these same values in the main block until the Ctrl+C signal is seen. At which point the closure will call a function that displays some stats about the values.
Specifically, I am recreating ping
. I have variables requests
and replies
that track the number of each that occurs during the program's life. When the user input Ctrl+C, the program will display the total requests, replies, and the percentage of packets lost.
Example:
fn ping(ip: IpAddr, frequency: u32) -> () {
let requests = 0;
let replies = 0;
ctrlc::set_handler(move || calc_stats(requests, replies)).expect("");
//each echo request increments request by 1
//each echo reply increments replies by 1
//eventually user enter Ctrl+C, the closure is called and in turn calc_stats is called
Now I know the code above will not work because requests and replies would be moved. But I tried using a Box and even Arc but run into an assortment of errors. The Box approach works when I clone:
let closure = {
let crequests = requests.clone();
let creplies = replies.clone();
ctrlc::set_handler(move || calc_stats(crequests.clone(), creplies.clone(), &min, &max, &rtts))
.expect("Could not calculate stats");
};
But the requests and replies variables are still their initial values (0).
What is the ideal approach in this scenario?
Arc
is the correct move. However, it doesn't allow you to get an &mut
to the contents, so you need an interior mutability construct, like Mutex
.
pub fn ping(ip: IpAddr, frequency: u32) {
let requests = Arc::new(Mutex::new(0));
let replies = Arc::new(Mutex::new(0));
let state_clone = state.clone();
ctrlc::set_handler(move || calc_stats(&requests, &replies)).unwrap();
}
fn calc_stats(requests: &Mutex<i32>, replies: &Mutex<i32>) {
todo!()
}
It is a bit inefficient to have two mutexes here, so you could put all your state variables into a struct.
struct State {
requests: u32,
replies: u32,
}
impl State {
fn new() -> Self {
State {
requests: 0,
replies: 0,
}
}
}
And then wrap that State
in a mutex. Here's a whole example, minus the actual pinging.
pub fn ping(ip: IpAddr, frequency: u32) {
let state = Arc::new(Mutex::new(State::new()));
let state_clone = state.clone();
ctrlc::set_handler(move || calc_stats(&state_clone)).unwrap();
do_things(&state, ip, frequency);
}
fn calc_stats(state: &Mutex<State>) {
let state = state.lock().unwrap();
println!("Requests: {}\nReplies: {}", state.requests, state.replies);
std::process::exit(0);
}
fn do_things(state: &Mutex<State>, ip: IpAddr, frequency: u32) {
loop {
{
println!("Request");
let mut state = state.lock().unwrap();
state.requests += 1;
}
std::thread::sleep(std::time::Duration::from_secs(1));
{
println!("Reply");
let mut state = state.lock().unwrap();
state.replies += 1;
}
}
}
Just ensure you leave the mutex unlocked at some point so that the ctrlc
handler can read it.
A more performant implementation would use the Atomic*
integers instead of Mutex
, but Mutex
is more general and easier to use.
Also some general advice, more errors does not necessarily mean your code is more incorrect.