Generic statics within generic functions

First off, please forgive my ignorance towards the current implementation and limitations of the compiler. The following may or may not be reasonably feasible.

I'm not currently advocating for global generic statics as that can be tricky to implement correctly. I do, however, assume that static generics within a function body should be fairly trivial (to reason about, not necessarily implement). I imagine the instance of the static can be created at the same time as the instance of the function.

This is a pattern I've encountered recently when implementing some of my libraries. This would theoretically allow libraries to create static data for users types.

I apologize for the following obscure example that probably has countless better solutions. My current use case, in my library, is to create a single instance of an objc subclass of NsWindow for each users type that implements a WindowImpl trait. The only other ways that keeps this abstracted away from the user is to create a new subclass every time a Window<impl WindowImpl> is created or to cache the subclasses with a static CACHE: Mutex<HashMap<TypeId, ObjcClass>>.

// Library Code

trait SomeTrait { .. }

type ExecFn<T> = fn (this: &T)

struct Operation<T: SomeTrait> {
    name: &'static str,
    exec: ExecFn<T>
}

fn get_operation<T: SomeTrait> -> &'static Operation<T> {
    // could easily be name mangled to the rust equivalent of 
    // get_operation_SomeConcreteType_INSTANCE
    static INSTANCE: Operation<T> = Operation {
        name: "add",
        exec: |_| { /* do add operation */ }
    };
    &INSTANCE
}

fn get_operation_2<T: SomeTrait> -> &'static Operation<T> {
    static INSTANCE: Operation<T> = Operation {
        name: "sub",
        exec: |_| { /* do sub operation */ }
    };
    &INSTANCE
}

// User Code

struct SomeConcreteType { .. }

impl SomeTrait for SomeConcreteType { .. }

fn execute() {
    let this = SomeConcreteType { .. };
    get_operation::<SomeConcreteType>().exec(&this);
    get_operation2::<SomeConcreteType>().exec(&this);
}

The reason generic statics are not provided is that statics in Rust guarantee address stability (uniqueness), and there is no known way to guarantee that for generic statics across crates and codegen units.

1 Like

Are you talking about monomorphization at compile time here? There's no dymanic generation of statics or functions.

Are you sure you don't want consts? For example.

2 Likes

The problem is that there is not “the” instance of the function even for a specific set of type arguments; there are potentially many instances of the function, created while compiling many different crates and different codegen units (CGUs). These duplicate functions’ machine code later gets deduplicated by the linker (if you are doing static linking), but the code is a simpler problem because it is neither mutable nor guaranteed a unique address.

1 Like

Thank you, that is a way I haven't though of using traits and am now 100 percent going to use in the future. Unfortunately, that works for that bad example I gave, but I ultimately need a static LazyLock.

I have cross-platform windowing library that I'm cleaning up and slowly releasing on crates. One of my goals was to remove all dynamic dispatch between the os to the user. For Windows, I can just set the windows winproc address to a fn win_proc<I: WindowImpl>(..) which can then statically call functions on the provided WindowImpl type.

For macOS, I have to register a whole objc subclass of NSWindow with the objc runtime. My current unreleased version uses a single subclass of NSWindow that dynamically calls functions on a Box<dyn WindowImpl>. If I had generic statics it would be trivial to register a new subclass of NSWindow that can statically dispatch WindowImpl functions.

This might totally be a skill issue but the best solution I've landed on that keeps the class registration internal to the library is to just cache each instance in a static HashMap<TypeId, ObjcClass> base on the TypeId of the type implementing WindowImpl. This solution is fine and ironically seems to keep the spirit of objc but I've just run into a couple instances where generic statics would create a quick and painless solution if feasible to implement.

Still pseudocode but closer to the actual scenario :

trait WindowImpl: Sized {
    fn on_key(this: &mut Window<Self>, event: KeyEvent)
}

struct Window<I: WindowImpl> {
    ptr: *const NSWindow,
    _marker: core::marker::PhantomData<I>,
}

fn get_objc_window_class<I: WindowImpl>() -> ObjcWindowClass<I> {
    static INSTANCE: LazyLock<ObjcWindowClass<I>> = LazyLock::new(|| {
        // register NSWindow class implementation with objective c runtime
        // directs callbacks to `WindowImpl` instance variable (ie. on_key, etc.)
    })
    &INSTANCE
}

impl<I: WindowImpl> Window<I> {
    pub fn new(implementation: I) -> Self {
        let class = get_objc_window_class::<I>()
        let ptr = class.alloc();
        ptr.init_with_impl(implemenation)
        Self {
            ptr
            _marker: core::marker::PhantomData 
        }
    }
}

Thanks for the rundown. Definitely some hopeful thinking on my part.

It seems to me that making this statically dispatched is unlikely to have any significant benefit, since

  • there is already an unavoidable Objective-C dynamic dispatch, so you can't get the inlining-and-optimization benefits of fully static dispatch, and
  • window events don’t generally arrive at a high enough rate that the slight costs of one extra function call will be significant.

It 100% percent won't. In the windowing library case it was just cool that on Windows a user of the library could implement the trait and it was pretty much guaranteed that their code would get inlined directly into the winproc. It more or less would just be an achievement to me if I could deliver the shortest path from OS to callback.

What ultimately prompted my post is that I started writing a mips emulator which has the same type of DI pattern where you can inject behavior into it to emulate different cpu models. I ultimately ran into another situation where generic statics would have been a very fast and "elegant" solution.

It's definitely a "me" problem. Not necessary by any stretch of the imagination. It was just something that I encountered more than once recently and thought it would be "a cool to have".

Do you actually care about it being a single static?

Because if you're just trying to get a &'static, you can do that by returning &const { ... } and relying on rvalue promotion.


Said otherwise, the things that are easy to implement are already supported by putting them in const blocks. Generic statics aren't supported because there's no known (good) way to solve the duplication problems, but consts are allowed to be duplicated so it's fine.

I was totally unaware you could return a reference to a const. That may actually be a good solution to one of the scenarios I ran into. I will have to do some further experimentation to see if I could make that work but it looks promising.

In my reply to @quinedot, I gave a better example which highlights the first time I ran into this. In that scenario I would need a single instance of the static due to it registering an class with the Objective-C runtime.

There are plenty of alternative solutions, however, I think it could be a useful language feature for libraries, if it was feasible.

Right now it seems like the three solutions to the scenarios I've ran into are :

  • make a new instance of the same data on every new
    • keeps the burden of data creation inside the library
    • unnecessary data duplication (fortunately in reality the size of this data is probably small)
    • doesn't necessarily work in situations where a singleton is needed
  • cache each created instance of this data in a HashMap for reuse
    • burden still contained within library
    • added complexity of lookup and creation if not found
  • make the user create the static data for each of their types
    • pushes the burden of data generation onto the user of the library
    • static INSTANCE: SomeRandomLibraryData<UserType> = SomeRandomLibraryData::new() seems pretty gross for an Api.

They all work fine. Generic statics just seem like a way more "clean" and "elegant" way for libraries to "generate" data for types made by users.

Keep in mind that you can provide a simple macro_rules! macro for the “force each implementor to provide their own static” approach. Like library_static!(UserType); or whatever.

Maybe have fn get_static() -> &'static LibraryStatic<Self> be a trait method of WindowImpl, and library_static!(UserType); could expand to an implementation of that method. IIRC, macros can expand to entire method implementations (function signature and all).