Any way to create a generic static?

I think the answer is that this isn't possible, but I'd love to be able to create a static that is generic, e.g. within a function or a trait. Since the compiler monomorphizes the function anyways, is there any reason why we couldn't write a function like:

fn get_atomic_ptr<T: 'static>() -> &'static AtomicPtr<T> {
  static ptr: AtomicPtr<T> = AtomicPtr::new(std::ptr::null_mut());
  &ptr
}

I know this doesn't work as written, because generic statics aren't supported, and when you declare a static within a generic function there is just one static variable.

To me this seems an eyesore, that I need to write code like this in order to work around at runtime something that ought to be achievable at compile time.

Any thoughts why generic statics aren't possible, or whether there is a nicer workaround?

Here's a much easier workaround:

fn get_atomic_ptr<T: Send + Sync + 'static>() -> MappedMutexGuard<'static, AtomicPtr<T>> {
    static TMAP: Lazy<Mutex<ShareMap>> = Lazy::new(|| Mutex::new(ShareMap::custom()));

    MutexGuard::map(
        TMAP.lock(),
        |map| map.entry::<KeyWrapper<T>>().or_insert(AtomicPtr::new(ptr::null_mut()))
    )
}

Unlike with functions, duplicating generics can be behavior changing in case of interior mutability. It is impossible to prevent generic items being duplicated in the general case. If two crates use the same generic instantiation, they would either have to codegen it separately (as rustc currently does) or have some way of delaying codegen until link time (which is only really possible with LTO, and even then doesn't fix dynamic libraries).

1 Like

Ah, so the issue is that the "mono" in "monomorphizing" doesn't imply that you do it exactly once per type. I can see how this could be a problem.

But dynamic libraries can't contain generics, can they? For pretty much the same reason why dyn Trait methods can't be generic – there would be no way to codegen yet-unknown instantiations. So dylibs don't need to be fixed.

Note that C++ for example does support generic statics with the expected semantics, so it appears to be at least technically possible. Whether there are any other gotchas I don't know off the top of my head.

They can. You can dynamically link libstd which contains a lot of generics. Same for rustc_driver which rustc dynamically links to.

But how do you use it? Surely then libstd needs to be linked against by rustc in order to instantiate said generics, so the dylib is not the final product of the compilation process. This means that there is still room for compile-time/link-time arbitration after the creation of the dylib.

A rust dylib can be dlopened, in which case there is no such final link-time arbitration possible. This is done for codegen backends by rustc for example.

I'm confused. Are you saying that you can dlopen libstd.dylib, then somehow usefully access a generic inside by instantiating it with concrete types, purely at dynamic-link-time, without having to rely on rustc? And if not, then why is it a problem that uninstantiable generics exist in the library?

You can't access generics directly using dlopen. However imgine you have a dylib exposing a generic. Then two different dylibs call the generic with the same substitutions from #[no_mangle] functions. If you then dlopen these two dylibs there is absolutely no way to prevent duplicating the generic function with identical substitutions.

Regarding

know that you can use a const "place" in an array repetition expression, thence bypassing the need for Copy:

const EMPTY: Mutex<TypeHolderSend> =
    ::parking_lot::const_mutex(TypeHolderSend::new())
;
let containers = [EMPTY; INTERN_CONTAINER_COUNT];

Regarding the problem at hand, yeah, a map from TypeIds to Box<dyn Any + Send + Sync> is the usual workaround, either written as simply as that when performance doesn't matter, or fine-tuning it like you have.

There is another approach, for the case that ought to be doable at compile-time, which is when you know, beforehand, the set of types that may be involved, and if you really need unicity of the shared static[1]:

trait GetAtomicPtr : 'static + Sized {
    fn get_static ()
      -> &'static AtomicPtr<Self>
    ;
}

#[inline]
fn get_atomic_ptr<T : GetAtomicPtr>()
  -> &'static AtomicPtr<T>
{
    T::get_static()
}

macro_rules! get_atomic_ptr {(
    $( $T:ty ),* $(,)?
) => ($(
    impl GetAtomicPtr for $T {
        #[inline]
        fn get_static ()
          -> &'static AtomicPtr<$T>
        {
           static PTR: AtomicPtr<$T> = AtomicPtr::new();
           &PTR
        }
    }
)*)}

// Your known-beforehand / pre-hard-coded list of supported types here:
get_atomic_ptr![
    u8, u16, u32, usize, u64, 
    i8, i16, i32, isize, i64, 
    String,
    Vec<u8>,
];
get_atomic_ptr![
    SomethingElse,
];

  1. the latter point is referring to the fact that in Rust you can have a static S: T or you can have a const AT_S: &'static T; with the latter actually being easy to feature generically, but with the caveat that different AT_S instantiations may lead to duplicated static storage; so this is only useful when the statics are playing a semantically meaningless / only-for-performance operation, such as memoization ↩ī¸Ž

5 Likes

That's brilliant, thanks for the code review and suggestion!

1 Like

With const blocks it's going to be even easier:

let containers = [const { ::parking_lot::const_mutex(TypeHolderSend::new()) }; INTERN_CONTAINER_COUNT];

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.