0
votes

I want to change a mutable variable in multiple threads. I know this would not be thread-safe, but I want to know how the Rust compiler will deal with it. So I use a range's map function to spawn sub-threads:

use std::thread;

fn break_law(value: &mut i32) {
    *value += 20;
}

fn main() {
    let mut x = 10;

    let handles = (0..10).map(|| {
        thread::spawn(move || {
        break_law(&mut x);
        println!("{:?}", x);
        })
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}

But I get an error:

break_law1.rs:10:24: 15:4 error: type mismatch: the type `[closure@break_law1.rs
:10:28: 15:3 x:_]` implements the trait `core::ops::FnMut<()>`, but the trait `c
ore::ops::FnMut<(_,)>` is required (expected tuple, found ()) [E0281]
break_law1.rs:10        let handles = (0..10).map(|| {
break_law1.rs:11                thread::spawn(move || {
break_law1.rs:12                        break_law(&mut x);
break_law1.rs:13                        println!("{:?}", x);
break_law1.rs:14                })
break_law1.rs:15        }).collect();
break_law1.rs:10:24: 15:4 help: run `rustc --explain E0281` to see a detailed ex
planation
break_law1.rs:10:24: 15:4 error: type mismatch: the type `[closure@break_law1.rs
:10:28: 15:3 x:_]` implements the trait `core::ops::FnOnce<()>`, but the trait `
core::ops::FnOnce<(_,)>` is required (expected tuple, found ()) [E0281]
break_law1.rs:10        let handles = (0..10).map(|| {
break_law1.rs:11                thread::spawn(move || {
break_law1.rs:12                        break_law(&mut x);
break_law1.rs:13                        println!("{:?}", x);
break_law1.rs:14                })
break_law1.rs:15        }).collect();
break_law1.rs:10:24: 15:4 help: run `rustc --explain E0281` to see a detailed ex
planation
break_law1.rs:15:5: 15:14 error: type mismatch: the type `[closure@break_law1.rs
:10:28: 15:3 x:_]` implements the trait `core::ops::FnMut<()>`, but the trait `c
ore::ops::FnMut<(_,)>` is required (expected tuple, found ()) [E0281]
break_law1.rs:15        }).collect();
                           ^~~~~~~~~
break_law1.rs:15:5: 15:14 help: run `rustc --explain E0281` to see a detailed ex
planation
break_law1.rs:18:6: 18:14 error: the type of this value must be known in this co
ntext
break_law1.rs:18            h.join().unwrap();
                            ^~~~~~~~
break_law1.rs:17:2: 19:3 note: in this expansion of for loop expansion
error: aborting due to 4 previous errors

According to the info above, I get that range's map function definition is like this:

// Creates a new iterator that will apply the specified function to each
// element returned by the first, yielding the mapped element instead.
fn map<B, F>(self, f: F) -> Map<Self, F> 
    where F: FnMut(Self::Item) -> B

This looks odd, but how should I correct this? And why?

2

2 Answers

0
votes

The type signature of map is saying it takes type parameter (F) that is a closure (FnMut) that gets passed one argument that is the type the iterator yields ((Self::Item)) and returns a B (-> B). The piece you're missing is the argument: it needs to be .map(|x| { ... }) instead of .map(|| { ... }).

If you don't care about the value of the argument, you can write .map(|_| { ... }), using the _ pattern to ignore it, or .map(|_i| { ... }), where the leading _ is a convention to indicate that a variable is meant to be unused (it silences the compiler's normal warnings about unused variables).

FWIW, the error message is kinda long, but it does include this info:

type mismatch: the type `[closure@break_law1.rs:10:28: 15:3 x:_]` implement
    the trait `core::ops::FnMut<()>`, 
but the trait `core::ops::FnMut<(_,)>` is required

I've put line breaks to highlight the difference: the compiler is complaining that the closure you're passing to map takes no arguments (FnMut<()>), when it actually wants one that takes one (FnMut<(_,)>, the _ indicates a type parameter that hasn't got enough info to fully inferred yet).

0
votes

By the way, even after fixing parameters number issue as shown above, you will not get the behavior you wanted. The code will compile successfully because each thread will receive a copy of variable x, as long as integral types in Rust implements Copy trait.

I suppose the right example should look like this:

fn break_law(value: &mut i32) {
    *value += 20;
}

fn main() {
    let mut x = 10;
    let ref_x = &mut x;

    let handles: Vec<_> = (0..10).map(|_| {
        thread::spawn(move || {
            break_law(ref_x);
            println!("{:?}", x);
        })
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}