Home > database >  How do I change the structure in the thread?
How do I change the structure in the thread?

Time:11-28

Please help me, I'm completely confused.

How do I make this code work?

I need to change the structure members in the thread...

#[derive(Debug)]
struct S {
    str: String,
    b: bool,
    i: u128,
}

fn main() {
    let mut vec_s = vec![];

    vec_s.push(S {
        str: "a".to_string(),
        b: false,
        i: 0,
    });

    let mut threads = vec![];

    for s in vec_s {
        {
            let mut _s = &s;
            threads.push(std::thread::spawn(move || {
                _s.b = true;
                _s.str = "b".to_string();
                _s.i = 1;
            }));
        }
    }

    for thread in threads {
        let _ = thread.join();
    }

    dbg!(&vec_s);
}

The compiler outputs a lot of errors:

error[E0594]: cannot assign to `_s.b`, which is behind a `&` reference
  --> src/main.rs:23:17
   |
23 |                 _s.b = true;
   |                 ^^^^^^^^^^^ cannot assign

error[E0594]: cannot assign to `_s.str`, which is behind a `&` reference
  --> src/main.rs:24:17
   |
24 |                 _s.str = "b".to_string();
   |                 ^^^^^^ cannot assign

error[E0594]: cannot assign to `_s.i`, which is behind a `&` reference
  --> src/main.rs:25:17
   |
25 |                 _s.i = 1;
   |                 ^^^^^^^^ cannot assign

error[E0597]: `s` does not live long enough
  --> src/main.rs:21:26
   |
21 |               let mut _s = &s;
   |                            ^^ borrowed value does not live long enough
22 |               threads.push(std::thread::spawn(move || {
   |  __________________________-
23 | |                 _s.b = true;
24 | |                 _s.str = "b".to_string();
25 | |                 _s.i = 1;
26 | |             }));
   | |______________- argument requires that `s` is borrowed for `'static`
27 |           }
28 |       }
   |       - `s` dropped here while still borrowed

error[E0382]: borrow of moved value: `vec_s`
   --> src/main.rs:34:10
    |
9   |     let mut vec_s = vec![];
    |         --------- move occurs because `vec_s` has type `Vec<S>`, which does not implement the `Copy` trait
...
19  |     for s in vec_s {
    |              ----- `vec_s` moved due to this implicit call to `.into_iter()`
...
34  |     dbg!(&vec_s);
    |          ^^^^^^ value borrowed here after move
    |
note: this function takes ownership of the receiver `self`, which moves `vec_s`
   --> /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/collect.rs:261:18
    |
261 |     fn into_iter(self) -> Self::IntoIter;
    |                  ^^^^
help: consider iterating over a slice of the `Vec<S>`'s content to avoid moving into the `for` loop
    |
19  |     for s in &vec_s {
    |               

CodePudding user response:

You are attempting to do multithreading. Everything accessed by multiple threads has to be thread-safe. Further, with Rust being very strict (zero undefined behaviour tolerance), the compiler has to understand that your code is thread-safe.

The biggest problem here is that Rust's borrow checker doesn't understand that your threads get joined at some point. Therefore it doesn't allow you to create references to your S objects, because it cannot prove how long those references exist. Rust must be able to prove that they are destroyed before your S object is dropped; that's part of Rust's borrow checker safety guarantees.

For your specific usecase, Rust introduced thread scopes. With them, the compiler can finally understand thread lifetimes.

With some minor ownership issues fixed (use &mut vec_s for your loop, otherwise the vec_s object gets consumed by the loop), this now works:

#[derive(Debug)]
struct S {
    s: String,
    b: bool,
    i: u128,
}

fn main() {
    let mut vec_s = vec![];

    vec_s.push(S {
        s: "a".to_string(),
        b: false,
        i: 0,
    });

    std::thread::scope(|scope| {
        let mut threads = vec![];
        for s in &mut vec_s {
            threads.push(scope.spawn(move || {
                s.b = true;
                s.s = "b".to_string();
                s.i = 1;
            }));
        }
        for thread in threads {
            let _ = thread.join();
        }
    });

    dbg!(&vec_s);
}
[src/main.rs:31] &vec_s = [
    S {
        s: "b",
        b: true,
        i: 1,
    },
]

Another optimization:

If you don't actually use the JoinHandles for error propagation or similar, you don't need them at all in this example. The scope already automatically joins all threads it spawned at the end of the scope:

#[derive(Debug)]
struct S {
    s: String,
    b: bool,
    i: u128,
}

fn main() {
    let mut vec_s = vec![];

    vec_s.push(S {
        s: "a".to_string(),
        b: false,
        i: 0,
    });

    std::thread::scope(|scope| {
        for s in &mut vec_s {
            scope.spawn(move || {
                s.b = true;
                s.s = "b".to_string();
                s.i = 1;
            });
        }
    });

    dbg!(&vec_s);
}
[src/main.rs:27] &vec_s = [
    S {
        s: "b",
        b: true,
        i: 1,
    },
]
  • Related