1

I'm trying to build a threadpool where each thread in the pool has a thread_local! type which can be used by tasks on that worker thread. (T in the example below). The key purpose of the class is that the resource T need not be Send, since it will be constructed locally on each worker thread by a factory method which is Send.

My use case is to do work on a pool of !Send db connections, but I tried to make it generic over the resource type T.

extern crate threadpool;

use std::sync::mpsc::channel;
use std::sync::Arc;

// A RemoteResource is a threadpool that maintains a threadlocal ?Send resource
// on every pool in the thread, which tasks sent to the pool can reference.
// It can be used e.g., to manage a pool of database connections.
struct RemoteResource<T, M>
where
    M: 'static + Send + Sync + Fn() -> T,
{
    pool: threadpool::ThreadPool,
    make_resource: Arc<M>,
}

impl<T, M> RemoteResource<T, M>
where
    M: Send + Sync + Fn() -> T,
{
    pub fn new(num_workers: usize, make_resource: M) -> Self {
        RemoteResource {
            pool: threadpool::ThreadPool::new(num_workers),
            make_resource: Arc::new(make_resource),
        }
    }

    pub fn call<F, R>(&mut self, f: F) -> R
    where
        R: 'static + Send,
        F: 'static + ::std::marker::Send + FnOnce(&mut T) -> R,
    {
        let (tx, rx) = channel();
        let maker = self.make_resource.clone();
        self.pool.execute(move || {
            use std::cell::RefCell;
            thread_local!{
                static UNSENDABLE_TYPE: RefCell<Option<T>> = RefCell::new(None)
            }
            UNSENDABLE_TYPE.with(|it| {
                let mut mine = it.borrow_mut();
                if mine.is_none() {
                    *mine = Some(maker());
                }
                if let Some(ref mut mine) = *mine {
                    let res = f(mine);
                    tx.send(res).unwrap();
                    return ();
                }
                unreachable!()
            });
        });
        rx.recv().unwrap()
    }
}

(Playground)

Unfortunately I can't get my code to typecheck when I abstract over T:

error[E0401]: can't use type parameters from outer function
  --> src/lib.rs:38:56
   |
17 | impl<T, M> RemoteResource<T, M>
   |      - type variable from outer function
...
28 |     pub fn call<F, R>(&mut self, f: F) -> R
   |            ---- try adding a local type parameter in this method instead
...
38 |                 static UNSENDABLE_TYPE: RefCell<Option<T>> = RefCell::new(None)
   |                                                        ^ use of type variable from outer function

I tried to resolve this using the suggestions in the Rust Compiler Error Index, but "copying the type over" doesn't work. If if "copy" T into call, then I get a "shadowed type variable" error. If I introduce a new type U, then I get a very confusing E401 again, but this time suggesting that I try to add a type parameter to the type parameters on call, exactly where I've actually already added it. This second thing looks like a bug in the compiler to me. (If you're curious about that, here's the playground).

Is it possible to get this to typecheck? If not, why not?

masonk
  • 9,176
  • 2
  • 47
  • 58
  • `?Send` — it is [not possible to define such a bound](https://stackoverflow.com/questions/30333607/what-does-the-question-mark-mean-in-a-type-parameter-bound); you may only say `?Sized`. – Shepmaster Sep 27 '18 at 23:29
  • Good to know. I reworded for clarity. – masonk Sep 27 '18 at 23:35
  • The error message is bad – see also [this bug report](https://github.com/rust-lang/rust/issues/54560) – Sven Marnach Sep 28 '18 at 10:38

1 Answers1

6

It has nothing to do with the closure and everything to do with static. Here's a smaller example that produces the same error:

fn foo<T>() {
    static BAR: Option<T> = None;
}

A static in a generic function will not generate one static for each T; there is only one static variable. Take the following program, for example:

fn foo<T>() {
    static mut COUNTER: i32 = 0;
    unsafe {
        COUNTER += 1;
        println!("{}", COUNTER);
    }
}

fn main() {
    foo::<i32>();
    foo::<u64>();
}

This prints:

1
2

Here, foo::<i32> and foo::<u64> both share the same counter. Considering this, it doesn't make sense to define a single static whose type depends on the type parameter of its enclosing generic function, since that function can be instantiated multiple times.

Unfortunately, there's no way to define a "generic static" that is instantiated for each T that is used. What you can do instead is define some sort of typemap (i.e. a map from TypeId to Box<Any>) and perform a dynamic lookup in that map.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • 1
    *A static in a generic function will not generate one static for each `T`* — any reason you know of for why? I'm asking around, but I can't figure out any specific reason; it seems like it could be a form of monomorphization. – Shepmaster Sep 28 '18 at 01:05
  • When everything is built statically, that could work, but if the application uses dynamically linked libraries, there could be multiple instantiations of the same variable. For example, crate A defines a generic `static` but doesn't instantiate it, then crates B and C both instantiate it for the same type. For functions, this is generally not a problem because we (often) don't care that there are two identical functions. – Francis Gagné Sep 28 '18 at 03:35
  • Thanks for this fantastic answer! Much appreciated. – masonk Sep 28 '18 at 06:04
  • @FrancisGagné won't that happen in the "normal" case as well? Won't two dylibs that each define a `static FOO` (even from the same shared crate) have distinct memory addresses? – Shepmaster Sep 28 '18 at 16:22
  • 1
    @Shepmaster If you try to build a binary with a mix of static and dynamic libs, you end up with errors like `error: cannot satisfy dependencies so \`std\` only shows up once`, which means that in practice, I don't think you can have two dylibs that contain the same crate (that shared crate must be a dylib too). – Francis Gagné Sep 29 '18 at 20:03