masonk
masonk

Reputation: 9788

"Use of type variable from outer function" in a closure that creates a thread-local variable of that type

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?

Upvotes: 1

Views: 800

Answers (1)

Francis Gagn&#233;
Francis Gagn&#233;

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

Related Questions