SOLVED: Why do I need to clone the moved string here?

fn setup_file_watchers(tx_udp: &Sender<String>, handles: &mut Vec<JoinHandle<()>>) {
    // skip trash zone = 0
    for zone in 1..std::u8::MAX {
        let path = format!("/sys/class/thermal/thermal_zone{zone}/temp");
        if !Path::new(&path).exists() {
            // no more zones available on this system
            break;
        }
        if zone != 2 { // skip trash zone = 2
            let invalid_values: HashSet<String> = if zone == 11 {
                HashSet::from([String::from("0.05")])
            }
            else {
                HashSet::from([])
            };
            //handles.push(tokio::spawn(watch_file(path, zone, tx_udp.clone(), invalid_values)));
            let prefix = format!(",zone={zone} xps15manjaro.temperature=");
            handles.push(tokio::spawn(metrics_sender(tx_udp.clone(), prefix, invalid_values, move || {
                let value = fs::read_to_string(path.clone())
                    .expect("Should have been able to read the file");
                let parsed: f32 = value.trim().parse::<f32>().unwrap() / 1000.0;
                format!("{parsed}")
            })));
        }
    }
}

I thought the move would transfer ownership of path into that closure. Why do get this error if I don't clone it?

error[E0507]: cannot move out of `path`, a captured variable in an `Fn` closure
  --> src/main.rs:55:48
   |
40 |         let path = format!("/sys/class/thermal/thermal_zone{zone}/temp");
   |             ---- captured outer variable
...
54 |             handles.push(tokio::spawn(metrics_sender(tx_udp.clone(), prefix, invalid_values, move || {
   |                                                                                              ------- captured by this `Fn` closure
55 |                 let value = fs::read_to_string(path)
   |                                                ^^^^ move occurs because `path` has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.
error: could not compile `rust_playground_2022-11-07` due to previous error

UPDATE: I found out myself, that I can use an immutable reference instead of cloning. I can't (and don't want to) pass ownership of path into read_to_string because that could happen only once but it's a closure that needs to be callable repeatable and passing that ownership would consume path on the first run.

Fn closures can't modify their variables, I'd need FnMut if I would like to do that, which I don't want to do here.

I guess from your update you already figured it out, but I wrote this anyway since I wasn't totally sure, and FnOnce might be useful for you.

Fn closures can be called multiple times, so metrics_sender is allowed to call your closure multiple times. However, the first call moves path and the subsequent calls would no longer have access to it, hence the error. I think the compiler error could be improved here to make it clearer... When we clone path instead of moving it, the issue disappears because the closure can keep hold of path and just clone it on each call.

If you only intend for the closure to be called once, change the Fn to FnOnce in std::ops - Rust

1 Like