Reputation: 9788
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()
}
}
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?
Upvotes: 1
Views: 800
Reputation: 65782
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.
Upvotes: 6