How to rework code w/ "can't use type parameter from outer fn..."

Simplified I'm trying to do:

fn foo<T>() {
        static mut x : * const T = std::ptr::null::<T>();
}

rustc complains "can't use type parameters from outer function; try using a local type parameter instead". I'm not seeing how to adapt the workarounds from --explain to this particular example. Anyone have a suggestion?

I don’t think there’s a quick workaround. What are you trying to do in the grand scheme of things? If I had to guess based on that snippet, it’s some sort of lazily initialized singleton?

Yeah, lazy allocated singleton. I know how to do the lazy allocated, but the code is kind of gross, so I wanted to templatize it and stick it in a library. Looks like I can do it w/ a macro, but I preferred non-macro since it's easier to read.

I don't think there's going to be any elegant way to do this. Here's one way using thread local:

use std::collections::HashMap;
use std::cell::RefCell;
use std::any::{Any, TypeId};
use std::rc::Rc;

fn foo<T: Default + 'static>() -> Rc<RefCell<T>> {
    thread_local!(static MAP: RefCell<HashMap<TypeId, Rc<RefCell<Any>>>> = RefCell::new(HashMap::new()));

    MAP.with(|m| {
        let e = m.borrow_mut()
            .entry(TypeId::of::<T>())
            .or_insert_with(|| Rc::new(RefCell::new(T::default())))
            .clone();
        unsafe { Rc::from_raw(Rc::into_raw(e) as *const RefCell<T>) }
    })
}

fn foo2<T: 'static, F: FnOnce() -> T>(f: F) -> Rc<RefCell<T>> {
    thread_local!(static MAP: RefCell<HashMap<TypeId, Rc<RefCell<Any>>>> = RefCell::new(HashMap::new()));

    MAP.with(|m| {
        let e = m.borrow_mut()
            .entry(TypeId::of::<T>())
            .or_insert_with(|| Rc::new(RefCell::new(f())))
            .clone();
        unsafe { Rc::from_raw(Rc::into_raw(e) as *const RefCell<T>) }
    })
}

fn main() {
    let x = foo::<i32>();
    println!("{:?}", x);
    let s = foo2(|| "hello".to_string());
    println!("{:?}", s);
}

Playground

statics aren't all that great to work with in Rust (for good reason) so you may want to rethink the overall approach.

1 Like

Isn't "lazily instantiated singleton" exactly what lazy-static is for?

It is but I think this thread is a continuation of Any trick to initialize an RC for a static mut?, which I realized recently.

Requires Sync. Don't want that.
.

Yeah, this was a more narrow question, so I had hopes of a clever answer :slight_smile:

The map approach was WILDLY different from what I was thinking, so that's good. Will have to think about.

statics aren’t all that great to work with in Rust (for good reason) so you may want to rethink the overall approach.

Yeah. I don't agree w/ the good reason, but it's what it's. I'm writing a framework (well, re-writing in Rust) for model checking file systems for errors. I don't want to go modify the checked file system code (it will often be someone else's code) to pass around the current callback state. It's easier to insert a couple call outs and get the state they need from a global. Ugly. But better than having to modify lots of other-people code before the tool can run (they won't like it, won't accept patches --- for good reason --- , etc).

Currently I'm just using a macro. For the real code I want a "temporary" singleton that lasts for the duration of a session so it looks a little different in that there's ways to setup/reset. It does leak storage afaik.

macro_rules! make_handle {
        ($name: ident, $type:tt) => {
            mod $name {
                use super::$type;
                use std::cell::RefCell;
                use std::rc::Rc;
                use std::ptr::null;

                type T = Rc<RefCell<$type>>;
                fn internal_handle(t : *const T) -> T {
                        static mut singleton : *const T = null();
                        unsafe {
                                if t != null() {
                                        singleton = t;
                                }
                                assert!(singleton != null());
                                (*singleton).clone()
                        }
                }
                pub fn set_handle(t : $type) -> T {
                        let x = Box::new(Rc::new(RefCell::new(t)));
                        internal_handle(Box::into_raw(x))
                }
                pub fn get_handle() -> T { internal_handle(null()) }
            }
        }
}
#[derive(Debug)]
pub struct Handle(usize);
make_handle!(statecheck,Handle);
fn main() {
        let x = statecheck::set_handle(Handle { 0 : 0 });
        x.borrow_mut().0 += 11;

        let x = statecheck::get_handle();
        x.borrow_mut().0 += 1;

        let x = statecheck::get_handle();
        x.borrow_mut().0 += 1;
        println!("x={:?}", *x.borrow());
}

Note that the map approach could be simplified by using rust-typemap.

I can think of some… not recommended… alternatives:

#[link_section = ".data"]
fn dummy<T>(a: &&&&&&&&i32) -> i32 {
    // The code for this should take up at least 8 bytes... probably...
    ********a
}

fn foo<T>() {
    let ptr = dummy::<T> as *mut *const T;
    println!("{:?}", ptr);
}


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

note that that won't do the right thing if the same specialization is instantiated from multiple crates. or possibly ever.