1

I would like to write a function that accepts an integer, and spawns threads that use this integer. The integer could be computed. It doesn't need to be a literal. If I use a concrete type such as usize, it would work but when I try to generalize it, it fails to compile:

fn g<A>(a: A)
where
    A: num::PrimInt + Copy + std::fmt::Debug + Send,
{
    let hs = (0..3).map(|_| {
        std::thread::spawn(move || {
            println!("{:?}", a);
        })
    });

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

The error is:

1 | fn g<A>(a: A)
  |      - help: consider adding an explicit lifetime bound `A: 'static`...
...
6 |         std::thread::spawn(move || {
  |         ^^^^^^^^^^^^^^^^^^
  |
note: ...so that the type `[closure@src/main.rs:6:28: 8:10 a:A]` will meet its required lifetime bounds
 --> src/main.rs:6:9
  |
6 |         std::thread::spawn(move || {
  |         ^^^^^^^^^^^^^^^^^^

Since I have the Copy trait, it should be able to copy this value for each thread and hence the lifetime bound recommendation is not necessary. How do I resolve this?

Listerone
  • 1,381
  • 1
  • 11
  • 25
  • 2
    This recommendation _is_ necessary. Consider that all immutable references are `Copy`, regardless of their lifetime. – Michail Jun 20 '19 at 19:57
  • @Michail What reference? It is an integer and I would like to make a copy of it for every thread. – Listerone Jun 20 '19 at 20:07
  • 1
    True, but a reference type might one day implement `PrimInt`, and the compiler guards agains that. – Michail Jun 20 '19 at 20:08
  • @Michail That makes no sense. It is the purpose of Copy to indicate the value can be copied. – Listerone Jun 20 '19 at 20:10
  • I'm really bad at explaining things, so here's a question with an informative explanation: [here](https://stackoverflow.com/questions/49972233/why-do-i-need-to-provide-lifetimes-for-a-structs-generic-parameters-which-are-n/49972692#49972692) for a similar situation. – Michail Jun 20 '19 at 20:15
  • It would seem absurd if it is not possible to copy a generic integer to multiple threads ... – Listerone Jun 20 '19 at 21:46
  • I think you have two easy ways to do this. Since `a` is copy, you could re-assign it to a new `let` binding, which (as you point out) would copy it. Otherwise, since `Copy` requires `Clone`, you could explicitly `a.clone()`, which will also create a copy. – Zarenor Jun 20 '19 at 22:10
  • Note that this is expressed in the constraint `F: Send + 'static` on `std::thread::spawn`: https://doc.rust-lang.org/std/thread/fn.spawn.html – starblue Jun 21 '19 at 06:51
  • Also, `PrimInt` is a trait in the `num`crate and not in the standard library, so the compiler can't assume anything special about it. – starblue Jun 21 '19 at 06:54
  • `num::PrimInt` could (should?) add `'static` as a supertrait, then this would work. – starblue Jun 21 '19 at 07:02

3 Answers3

1

Copy and 'static are different constraints, and both are necessary for a value to be moved across threads. While you may only intend to pass integers (which satisfy both Copy and 'static) to your generic function, the compiler cannot know for sure. It must be able to prove that the function body is valid for all possible type parameters. An example of a type which is Copy but not 'static is a reference to a local variable:

fn assert_is_copy<T: Copy>(_: T) {}
fn assert_is_static<T: 'static>(_: T) {}

fn main() {
    let x: usize = 5;

    assert_is_copy(x);
    assert_is_static(x);
    assert_is_copy(&x);
    //assert_is_static(&x); // FAILS TO COMPILE
}

I think you'll agree that you don't want to be passing references to your stack variables to different threads (although it can be safe in some limited cases). If you did, you could detach that thread, pop the stack frame with the local variable on it, and cause undefined behavior!

This answer gives a simple, (mostly) correct explanation of what it means to say T: 'static.

ecstaticm0rse
  • 1,436
  • 9
  • 17
1

Others have explained why your code doesn't work, but if you're still wondering how to make it work, simply add + 'static to your constraints:

fn g<A>(a: A)
where
    A: num::PrimInt + Copy + std::fmt::Debug + Send + 'static,
{
    let hs = (0..3).map(|_| {
        std::thread::spawn(move || {
            println!("{:?}", a);
        })
    });

    for h in hs {
        h.join().unwrap();
    }
}
Jmb
  • 18,893
  • 2
  • 28
  • 55
  • As I said in my original question, the integer may be computed and is not a literal. `'static` does not work for my use case. – Listerone Jun 21 '19 at 13:10
-1

The direct answer to my question based on the other answers is the following:

In Rust, it is not possible to pass a computed, non-literal PrimInt to more than one thread.

Listerone
  • 1,381
  • 1
  • 11
  • 25
  • This is wrong. All primitive integer types (e.g. `u32`, `isize`) are `'static`. It does not matter whether they come from a literal or are computed at runtime. – ecstaticm0rse Jun 22 '19 at 17:21
  • All you needed to do to see this was to follow the compiler's recommendation (add a `'static` bound to `A`), then pass any integer value to `g`. – ecstaticm0rse Jun 22 '19 at 17:30