i'm new to rust.
I'm trying to write a file_sensor that will start a counter after a file is created. The plan is that after an amount of time, if a second file is not received the sensor will exit with a zero exit code.
I could write the code to continue that work but i feel the code below illustrates the problem (i have also missed for example the post function referred to)
I have been struggling with this problem for several hours, i've tried Arc and mutex's and even global variables.
The Timer implementation is Ticktock-rs
I need to be able to either get heartbeat in the match body for EventKind::Create(CreateKind::Folder) or file_count in the loop
The code i've attached here runs but file_count is always zero in the loop.
use std::env;
use std::path::Path;
use std::{thread, time};
use std::process::ExitCode;
use ticktock::Timer;
use notify::{
Watcher,
RecommendedWatcher,
RecursiveMode,
Result,
event::{EventKind, CreateKind, ModifyKind, Event}
};
fn main() -> Result<()> {
let now = time::Instant::now();
let mut heartbeat = Timer::apply(
|_, count| {
*count += 1;
*count
},
0,
)
.every(time::Duration::from_millis(500))
.start(now);
let mut file_count = 0;
let args = Args::parse();
let REQUEST_SENSOR_PATH = env::var("REQUEST_SENSOR_PATH").expect("$REQUEST_SENSOR_PATH} is not set");
let mut watcher = notify::recommended_watcher(move|res: Result<Event>| {
match res {
Ok(event) => {
match event.kind {
EventKind::Create(CreateKind::File) => {
file_count += 1;
// do something with file
}
_ => { /* something else changed */ }
}
println!("{:?}", event);
},
Err(e) => {
println!("watch error: {:?}", e);
ExitCode::from(101);
},
}
})?;
watcher.watch(Path::new(&REQUEST_SENSOR_PATH), RecursiveMode::Recursive)?;
loop {
let now = time::Instant::now();
if let Some(n) = heartbeat.update(now){
println!("Heartbeat: {}, fileCount: {}", n, file_count);
if n > 10 {
heartbeat.set_value(0);
// This function will reset timer when a file arrives
}
}
}
Ok(())
}
Your compiler warnings show you the problem:
warning: unused variable: `file_count`
--> src/main.rs:31:25
|
31 | file_count += 1;
| ^^^^^^^^^^
|
= note: `#[warn(unused_variables)]` on by default
= help: did you mean to capture by reference instead?
The problem here is that you use file_count inside of a move || closure. file_count is an i32, which is Copy. Using it in a move || closure actually creates a copy of it, which does no longer update the original variable if you assign to it.
Either way, it's impossible to modify a variable in main() from an event handler. Event handlers require 'static lifetime if they reference things, because Rust cannot guarantee that the event handler lives shorter than main.
One solution for this problem is to use reference counters and interior mutability. In this case, I will use Arc for reference counters and AtomicI32 for interior mutability. Note that notify::recommended_watcher requires thread safety, otherwise instead of an Arc<AtomicI32> we could have used an Rc<Cell<i32>>, which is the same thing but only for single-threaded environments, with a little less overhead.
use notify::{
event::{CreateKind, Event, EventKind},
RecursiveMode, Result, Watcher,
};
use std::time;
use std::{env, sync::atomic::Ordering};
use std::{path::Path, sync::Arc};
use std::{process::ExitCode, sync::atomic::AtomicI32};
use ticktock::Timer;
fn main() -> Result<()> {
let now = time::Instant::now();
let mut heartbeat = Timer::apply(
|_, count| {
*count += 1;
*count
},
0,
)
.every(time::Duration::from_millis(500))
.start(now);
let file_count = Arc::new(AtomicI32::new(0));
let REQUEST_SENSOR_PATH =
env::var("REQUEST_SENSOR_PATH").expect("$REQUEST_SENSOR_PATH} is not set");
let mut watcher = notify::recommended_watcher({
let file_count = Arc::clone(&file_count);
move |res: Result<Event>| {
match res {
Ok(event) => {
match event.kind {
EventKind::Create(CreateKind::File) => {
file_count.fetch_add(1, Ordering::AcqRel);
// do something with file
}
_ => { /* something else changed */ }
}
println!("{:?}", event);
}
Err(e) => {
println!("watch error: {:?}", e);
ExitCode::from(101);
}
}
}
})?;
watcher.watch(Path::new(&REQUEST_SENSOR_PATH), RecursiveMode::Recursive)?;
loop {
let now = time::Instant::now();
if let Some(n) = heartbeat.update(now) {
println!(
"Heartbeat: {}, fileCount: {}",
n,
file_count.load(Ordering::Acquire)
);
if n > 10 {
heartbeat.set_value(0);
// This function will reset timer when a file arrives
}
}
}
}
Also, note that the ExitCode::from(101); gives you a warning. It does not actually exit the program, it only creates an exit code variable and then discards it again. You probably intended to write std::process::exit(101);. Although I would discourage it, because it does not properly clean up (does not call any Drop implementations). I'd use panic here, instead. This is the exact usecase panic is meant for.
Related
I am trying to get the user input after a certain duration by using two threads. A thread duration and thread for editing. When the thread duration completes,and that the thread for editing has not completed,the terminal state is not restored thus breaking the terminal. This happens when the user did not press "q" before the time duration
The only way of restoring the state of the terminal is to press"q" which will break the loop in the first thread calling droop on the termion raw terminal
use std::io;
use std::io::Write;
use crossbeam_channel::{select, unbounded};
use std::thread;
use std::time;
use std::time::Duration;
use termion;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
fn test() -> String {
let (s1, r1) = unbounded();
let (s2, r2) = unbounded();
let terminal = io::stdout().into_raw_mode();
let mut stdout = terminal.unwrap();
let mut stdin = termion::async_stdin().keys();
thread::spawn(move || {
// Use asynchronous stdin
let mut s = String::new();
loop {
// Read input (if any)
let input = stdin.next();
// If a key was pressed
if let Some(Ok(key)) = input {
match key {
// Exit if 'q' is pressed
termion::event::Key::Char('q') => {
s1.send('q');
break;
}
// Else print the pressed key
_ => {
if let termion::event::Key::Char(k) = key {
s1.send(k);
}
stdout.lock().flush().unwrap();
}
}
}
thread::sleep(time::Duration::from_millis(50));
}
});
thread::spawn(move || {
thread::sleep(Duration::from_millis(3000));
s2.send(20).unwrap();
});
// None of the two operations will become ready within 100 milliseconds.
let mut val: String = String::new();
loop {
select! {
recv(r1) -> msg => val.push(msg.unwrap()),
recv(r2) -> _msg => break,
default(Duration::from_millis(3000)) => println!("timed out"),
};
}
return val;
}
fn main() {
println!("result {}", test());
}
In Rust, forcefully exiting a thread (such as by ending the main thread before the child threads run) is almost never a good idea, for reasons you've seen here. Their destructors don't get run, which means things could get messed up. The cleanest way is probably to keep an Arc<Mutex<bool>> that becomes true when threads should exit, and the threads can read it on their own accord and exit gracefully. Then, you should join the threads at the end of the function to ensure they finish all the way through. I've documented my changes in the comments:
use std::io;
use std::io::Write;
use crossbeam_channel::{select, unbounded};
use std::thread;
use std::time;
use std::time::Duration;
// import Arc and Mutex
use std::sync::{Arc, Mutex};
use termion;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
fn test() -> String {
let (s1, r1) = unbounded();
let (s2, r2) = unbounded();
let terminal = io::stdout().into_raw_mode();
let stdout = terminal.unwrap();
let mut stdin = termion::async_stdin().keys();
// keep a boolean flag of if we should exit
let should_exit = Arc::new(Mutex::new(false));
// clone the Arc for moving into the first thread
let should_exit_t1 = Arc::clone(&should_exit);
// keep a vec of handles for joining
let mut handles = vec![];
// push the handle onto the vec
handles.push(thread::spawn(move || {
loop {
// if the flag is true then we should gracefully exit
if *should_exit_t1.lock().unwrap() {
break;
}
// Read input (if any)
let input = stdin.next();
// If a key was pressed
if let Some(Ok(key)) = input {
match key {
// Exit if 'q' is pressed
termion::event::Key::Char('q') => {
s1.send('q').unwrap();
break;
}
// Else print the pressed key
_ => {
if let termion::event::Key::Char(k) = key {
s1.send(k).unwrap();
}
stdout.lock().flush().unwrap();
}
}
}
thread::sleep(time::Duration::from_millis(50));
}
}));
// also push the handle onto the vec
handles.push(thread::spawn(move || {
thread::sleep(Duration::from_millis(3000));
s2.send(20).unwrap();
}));
// None of the two operations will become ready within 100 milliseconds.
let mut val: String = String::new();
loop {
select! {
recv(r1) -> msg => val.push(msg.unwrap()),
recv(r2) -> _msg => break,
default(Duration::from_millis(3000)) => println!("timed out"),
};
}
// before exiting, set the exit flag to true
*should_exit.lock().unwrap() = true;
// join all the threads so their destructors are run
for handle in handles {
handle.join().unwrap();
}
return val;
}
fn main() {
println!("result {}", test());
}
I'm not sure I understand Rust's concurrency support with Mutexes and condition variables. In the following code, the main thread sets the poll_thread to be idle for two seconds, then to "read a register" for 2 seconds, and then return to "idle":
use std::thread;
use std::sync::{Arc, Mutex, Condvar};
use std::time;
#[derive(PartialEq, Debug)]
enum Command {
Idle,
ReadRegister(u32),
}
fn poll_thread(sync_pair: Arc<(Mutex<Command>, Condvar)>) {
let &(ref mutex, ref cvar) = &*sync_pair;
loop {
let mut flag = mutex.lock().unwrap();
while *flag == Command::Idle {
flag = cvar.wait(flag).unwrap();
}
match *flag {
Command::Idle => {
println!("WHAT IMPOSSIBLE!");
panic!();
}
Command::ReadRegister(i) => {
println!("You want me to read {}?", i);
thread::sleep(time::Duration::from_millis(450));
println!("Ok, here it is: {}", 42);
}
}
}
}
pub fn main() {
let pair = Arc::new((Mutex::new(Command::Idle), Condvar::new()));
let pclone = pair.clone();
let rx_thread = thread::spawn(|| poll_thread(pclone));
let &(ref mutex, ref cvar) = &*pair;
for i in 0..10 {
thread::sleep(time::Duration::from_millis(500));
if i == 4 {
println!("Setting ReadRegister");
let mut flag = mutex.lock().unwrap();
*flag = Command::ReadRegister(5);
println!("flag is = {:?}", *flag);
cvar.notify_one();
} else if i == 8 {
println!("Setting Idle");
let mut flag = mutex.lock().unwrap();
*flag = Command::Idle;
println!("flag is = {:?}", *flag);
cvar.notify_one();
}
}
println!("after notify_one()");
rx_thread.join();
}
This works as expected, but when the line to sleep for 450 milliseconds is uncommented, the code will often remain in the "read" state and not return to waiting on the condition variable cvar.wait(). Sometimes it will return to idle after, say, 15 seconds!
I would think that when poll_thread reaches the bottom of the loop, it would release the lock, allowing main to acquire and set flag = Command::Idle, and within roughly half a second, poll_thread would return to idle, but it appears that isn't happening when poll_thread sleeps. Why?
I am new to Rust and I am struggling with the concept of borrowing.
I want to load a Vec<Vec<f64>> matrix and then process it in parallel. However when I try to compile this piece of code I get error: capture of moved value: `matrix` [E0382] at the let _ = line.
This matrix is supposed to be readonly for the threads, they won't modify it. How can I pass it readonly and make the "moved value" error go away?
fn process(matrix: &Vec<Vec<f64>>) {
// do nothing for now
}
fn test() {
let filename = "matrix.tsv";
// loads matrix into a Vec<Vec<f64>>
let mut matrix = load_matrix(filename);
// Determine number of cpus
let ncpus = num_cpus::get();
println!("Number of cpus on this machine: {}", ncpus);
for i in 0..ncpus {
// In the next line the "error: capture of moved value: matrix" happens
let _ = thread::spawn(move || {
println!("Thread number: {}", i);
process(&matrix);
});
}
let d = Duration::from_millis(1000 * 1000);
thread::sleep(d);
}
Wrap the object to be shared into an Arc which stands for atomically reference counted (pointer). For each thread, clone this pointer and pass ownership of the clone to the thread. The wrapped object will be deallocated when it's no longer used by anything.
fn process(matrix: &Vec<Vec<f64>>) {
// do nothing for now
}
fn test() {
use std::sync::Arc;
let filename = "matrix.tsv";
// loads matrix into a Vec<Vec<f64>>
let mut matrix = Arc::new(load_matrix(filename));
// Determine number of cpus
let ncpus = num_cpus::get();
println!("Number of cpus on this machine: {}", ncpus);
for i in 0..ncpus {
let matrix = matrix.clone();
let _ = thread::spawn(move || {
println!("Thread number: {}", i);
process(&matrix);
});
}
let d = Duration::from_millis(1000 * 1000);
thread::sleep(d);
}
I modified an existing websocket client to save the message payload from the websocket server:
fn main()
{
let mut globaltest = "";
// ...some othercode
let receive_loop = thread::spawn(move || {
// Receive loop
for message in receiver.incoming_messages() {
let message: Message = match message {
Ok(m) => m,
Err(e) => {
println!("Receive Loop: {:?}", e);
let _ = tx_1.send(Message::close());
return;
}
};
match message.opcode {
Type::Close => {
// Got a close message, so send a close message and return
let _ = tx_1.send(Message::close());
return;
}
Type::Ping => match tx_1.send(Message::pong(message.payload)) {
// Send a pong in response
Ok(()) => (),
Err(e) => {
println!("Receive Loop: {:?}", e);
return;
}
},
// Say what we received
_ => {
println!("Receive Loop: {:?}", message);
// recive from motion
let buf = &message.payload;
{
let s = match str::from_utf8(buf) {
Ok(v) => v,
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
println!("{}",s);
////>>> problem here <<<
globaltest = s;
}
},
}
}
});
// ...some othercode
}
When I build, I get an error message:
nathaniel#nathaniel-virtual-machine:~/rustcoderep/rsdummywsclient$ sudo cargo build
Compiling rsdummywsclient v0.1.0 (file:///home/nathaniel/rustcoderep/rsdummywsclient)
src/main.rs:94:36: 94:51 error: `message.payload` does not live long enough
src/main.rs:94 let buf = &message.payload;
^~~~~~~~~~~~~~~
note: reference must be valid for the static lifetime...
src/main.rs:75:6: 106:4 note: ...but borrowed value is only valid for the block suffix following statement 0 at 75:5
src/main.rs:75 };
src/main.rs:76 match message.opcode {
src/main.rs:77 Type::Close => {
src/main.rs:78 // Got a close message, so send a close message and return
src/main.rs:79 let _ = tx_1.send(Message::close());
src/main.rs:80 return;
...
error: aborting due to previous error
I have no idea why. I tried a lot of solutions like Arc and Mutex, but none of them work :(
When I remove globaltest = s, the code builds and runs without problems. So I tried to write a simpler example:
use std::str;
use std::thread;
fn main() {
let mut y = 2;
let receive_loop = thread::spawn(move || {
let x = 1;
y = x;
println!("tt{:?}",y);
});
let receive_loop2 = thread::spawn(move || {
println!("tt2{:?}",y);
});
println!("{:?}",y);
}
This works... with almost the same structure.
Here is the full code, only a little different from the rust-websocket client sample:
extern crate websocket;
fn main() {
use std::thread;
use std::sync::mpsc::channel;
use std::io::stdin;
use std::str;
use websocket::{Message, Sender, Receiver};
use websocket::message::Type;
use websocket::client::request::Url;
use websocket::Client;
let mut globaltest ="";
let url = Url::parse("ws://127.0.0.1:2794").unwrap();
println!("Connecting to {}", url);
let request = Client::connect(url).unwrap();
let response = request.send().unwrap(); // Send the request and retrieve a response
println!("Validating response...");
response.validate().unwrap(); // Validate the response
println!("Successfully connected");
let (mut sender, mut receiver) = response.begin().split();
let (tx, rx) = channel();
let tx_1 = tx.clone();
let send_loop = thread::spawn(move || {
loop {
// Send loop
let message: Message = match rx.recv() {
Ok(m) => m,
Err(e) => {
println!("Send Loop: {:?}", e);
return;
}
};
match message.opcode {
Type::Close => {
let _ = sender.send_message(&message);
// If it's a close message, just send it and then return.
return;
},
_ => (),
}
// Send the message
match sender.send_message(&message) {
Ok(()) => (),
Err(e) => {
println!("Send Loop: {:?}", e);
let _ = sender.send_message(&Message::close());
return;
}
}
}
});
let receive_loop = thread::spawn(move || {
// Receive loop
for message in receiver.incoming_messages() {
let message: Message = match message {
Ok(m) => m,
Err(e) => {
println!("Receive Loop: {:?}", e);
let _ = tx_1.send(Message::close());
return;
}
};
match message.opcode {
Type::Close => {
// Got a close message, so send a close message and return
let _ = tx_1.send(Message::close());
return;
}
Type::Ping => match tx_1.send(Message::pong(message.payload)) {
// Send a pong in response
Ok(()) => (),
Err(e) => {
println!("Receive Loop: {:?}", e);
return;
}
},
// Say what we received
_ => {
println!("Receive Loop: {:?}", message);
// recive from motion
let buf = &message.payload;
{
let s = match str::from_utf8(buf) {
Ok(v) => v,
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
println!("{}",s);
globaltest = s;
}
},
}
}
});
loop {
let mut input = String::new();
stdin().read_line(&mut input).unwrap();
let trimmed = input.trim();
let message = match trimmed {
"/close" => {
// Close the connection
let _ = tx.send(Message::close());
break;
}
// Send a ping
"/ping" => Message::ping(b"PING".to_vec()),
// Otherwise, just send text
_ => Message::text(trimmed.to_string()),
};
match tx.send(message) {
Ok(()) => (),
Err(e) => {
println!("Main Loop: {:?}", e);
break;
}
}
}
// We're exiting
println!("Waiting for child threads to exit");
let _ = send_loop.join();
let _ = receive_loop.join();
println!("Exited");
}
#fjh thanks your reply!
I modified my code into this, changing globaltest in receive_loop and accessing it from the main thread loop. I still get a confusing error, even after spending three hours I still cannot solve it :(
fn main() {
let mut globaltest:Arc<Mutex<String>> = Arc::new(Mutex::new(String::from("")));
//some other code...
let receive_loop = thread::spawn(move || {
// Receive loop
for message in receiver.incoming_messages() {
let message: Message = match message {
Ok(m) => m,
Err(e) => {
println!("Receive Loop: {:?}", e);
let _ = tx_1.send(Message::close());
return;
}
};
match message.opcode {
Type::Close => {
// Got a close message, so send a close message and return
let _ = tx_1.send(Message::close());
return;
}
Type::Ping => match tx_1.send(Message::pong(message.payload)) {
// Send a pong in response
Ok(()) => (),
Err(e) => {
println!("Receive Loop: {:?}", e);
return;
}
},
// Say what we received
_ => {
let mut globaltest_child = globaltest.lock().unwrap();
println!("Receive Loop: {:?}", message);
// recive from motion
let buf = &message.payload;
{
let s = match str::from_utf8(buf) {
Ok(v) => v,
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
{
//>>> if I do like this, globaltest value will same like globaltest_child??
*globaltest_child = String::from(s);
println!("{:?}",globaltest_child.clone());
}
}
},
}
}
});
loop {
let message = Message::text("mtconnect");
match tx.send(message) {
Ok(()) => (),
Err(e) => {
println!("Main Loop: {:?}", e);
break;
}
}
///>>> problem here////
println!("{:?}",globaltest.clone());
thread::sleep(time::Duration::from_millis(3000));
}
}
The compiler always tells me:
athaniel#nathaniel-virtual-machine:~/rustcoderep/rsadapter$ sudo cargo run
Compiling rsadapter v0.1.0 (file:///home/nathaniel/rustcoderep/rsadapter)
src/main.rs:166:25: 166:35 error: use of moved value: `globaltest` [E0382]
src/main.rs:166 println!("{:?}",globaltest.clone());
^~~~~~~~~~
<std macros>:2:25: 2:56 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
src/main.rs:166:9: 166:45 note: in this expansion of println! (defined in <std macros>)
src/main.rs:166:25: 166:35 help: run `rustc --explain E0382` to see a detailed explanation
src/main.rs:102:35: 153:3 note: `globaltest` moved into closure environment here because it has type `alloc::arc::Arc<std::sync::mutex::Mutex<collections::string::String>>`, which is non-copyable
src/main.rs:102 let receive_loop = thread::spawn(move || {
src/main.rs:103
src/main.rs:104
src/main.rs:105 // Receive loop
src/main.rs:106 for message in receiver.incoming_messages() {
src/main.rs:107 let message: Message = match message {
...
src/main.rs:102:35: 153:3 help: perhaps you meant to use `clone()`?
error: aborting due to previous error
I still can't access globaltest in another thread.
When I remove globaltest = s, the code builds and runs without problems.
Yes, because that assignment is not safe to do. You're trying to modify a local variable declared in the main thread from within your other thread. That could lead to all sorts of problems, like data races, which is why the compiler won't let you do it.
It's difficult to say what the best way to fix this is without knowing more about what you want to do. That being said, you could probably fix this by making globaltest an Arc<Mutex<String>> instead of a &str, so you could safely access it from both threads.
Whenever you have a problem, you should spend time reducing the problem. This helps you understand where the problem is and is likely to remove extraneous details that may be confusing you.
In this case, you could start by removing all of the other match arms, replacing them with panic! calls. Then try replacing libraries with your own code, then eventually just standard library code. Eventually you will get to something much smaller that reproduces the problem.
This is called creating an MCVE, and is highly encouraged when you ask a question on Stack Overflow. However, it's 100% useful to yourself whenever you have a problem you don't yet understand. As a professional programmer, you are expected to do this legwork.
Here's one possible MCVE I was able to create:
use std::{str, thread};
fn main() {
let mut global_string = "one";
let child = thread::spawn(move || {
let payload = b"Some allocated raw data".to_vec();
let s = str::from_utf8(&payload).unwrap();
global_string = s;
});
println!("{}", global_string);
}
And it produces the same error ("reference must be valid for the static lifetime"). Specifically, the global_string variable is a &'static str, while s is a &str with a lifetime equivalent to the payload it is borrowed from. Simply put, the payload will be deallocated before the thread exits, which means that the string would point to invalid memory, which could cause a crash or security vulnerability. This is a class of errors that Rust prevents against.
This is what fjh is telling you.
Instead, you need to be able to ensure that the string will continue to live outside of the thread. The simplest way is to allocate memory that it will control. In Rust, this is a String:
use std::{str, thread};
fn main() {
let mut global_string = "one".to_string();
let child = thread::spawn(move || {
let payload = b"Some allocated raw data".to_vec();
let s = str::from_utf8(&payload).unwrap();
global_string = s.to_string();
});
println!("{}", global_string);
}
Now we've changed the error to "use of moved value: global_string", because we are transferring ownership of the String from main to the thread. We could try to fix that by cloning the string before we give it to the thread, but then we wouldn't be changing the outer one that we want.
Even if we could set the value in the outer thread, we'd get in trouble because we'd be creating a race condition where two threads are acting in parallel on one piece of data. You have no idea what state the variable is in when you try to access it. That's where a Mutex comes in. It makes it so that multiple threads can safely share access to one piece of data, one at a time.
However, you still have the problem that only one thread can own the Mutex at a time, but you need two threads to own it. That's where Arc comes in. An Arc can be cloned and the clone can be given to another thread. Both Arcs then point to the same value and ensure it is cleaned up when nothing is using it any more.
Note that we have to clone the Arc<Mutex<String>>> before we spawn the thread because we are transferring ownership of it into the thread:
use std::{str, thread};
use std::sync::{Arc, Mutex};
fn main() {
let global_string = Arc::new(Mutex::new("one".to_string()));
let inner = global_string.clone();
let child = thread::spawn(move || {
let payload = b"Some allocated raw data".to_vec();
let s = str::from_utf8(&payload).unwrap();
*inner.lock().unwrap() = s.to_string();
});
child.join().unwrap();
let s = global_string.lock().unwrap();
println!("{}", *s);
}
I have written a program to visit directories which is based
on the example on this page.
When I compile it, the compile displays the following 'note':
previous borrow of file_counter occurs here due to use in closure;
How can I display file_counter's value?
Is there a better (ie, more functional-like) way to count displayed files, in this program,
perhaps a non-mutable variable and/or recursion?
Many thanks.
fn main() {
let mut file_counter = 0i;
let display_path_closure = |path: &Path| {
file_counter += 1;
println!("{}) path = {}", file_counter, path.display());
};
let path = Path::new("z:/abc");
let _ = match visit_dirs(&path, display_path_closure) {
Err(e) => println!("error: {}", e),
Ok(_) => println!("Counter: {}", file_counter)
};
}
fn visit_dirs(dir: &Path, cb: |&Path|) -> io::IoResult<()> {
if dir.is_dir() {
let contents = try!(fs::readdir(dir));
for entry in contents.iter() {
if entry.is_dir() {
try!(visit_dirs(entry, |p| cb(p)));
} else {
cb(entry);
}
}
Ok(())
} else {
Err(io::standard_error(io::InvalidInput))
}
}
You can get around this by slight restructuring, putting the closure into an inner block:
fn main() {
let path = Path::new("z:/abc");
let mut file_counter = 0i;
let result = {
let display_path_closure = |path: &Path| {
file_counter += 1;
println!("{}) path = {}", file_counter, path.display());
};
visit_dirs(&path, display_path_closure)
};
let _ = match result {
Err(e) => println!("error: {}", e),
Ok(_) => println!("Counter: {}", file_counter)
};
}
As for why it happens, it is because closure captures all its environment by unique reference (mutable in your case), as if it is declared like this (pretending for a moment that closures capture their environment by value; in fact that's how unboxed closures work):
let mut file_counter = 0i;
let file_counter_ref = &mut file_counter;
// file_counter_ref is a plain pointer so it is copied into the closure,
// not taken by reference itself
let display_path_closure = |path: &Path| {
*file_counter_ref += 1;
println!("{}) path = {}", *file_counter_ref, path.display());
};
So file_counter_ref reference lasts to the end of the block in which closure is defined. In your case it is the whole main function starting from the closure declaration. I agree, this may be surprising and I certainly would also think that closure environment borrows die with the closure (e.g. when the closure is moved into the function and this function returns), but that's how things are now.
The situation with closures in Rust is currently unstable: unboxed closures have just been added to the language, so old boxed closures (like the one in this example) will soon go away; moreover, borrow checker is also being improved. These features may interact in complex ways, so maybe your original example will become possible soon :) (just a speculation, of course)