Static Self in traits, is there a way?

Suppose I have a trait that has a function like this one:

fn from_somewhere(arg: &ARG) -> Result<Self> {
    ...
}

Suppose I wanted to add another function:

// does not work of course
pub fn get_static(arg: &ARG) -> &'static Self {
    lazy_static! {
        // can't use Self here
        static ref RET: Self = Self::from_somewhere(arg)
            .expect("...");
    }
    &*RET
}

Is there a way to do this without resorting to using a macro instead of a trait?


EDIT: To clarify, Self does not work in lazy_static at all, the error:

error[E0401]: can't use generic parameters from outer function

adding a where Self: Sized bound to get_static might work.

You have to implement the function manually for every implementors.

@bjorn3 Not the issue. I clarified with an edit.

Unfortunately, if you do figure out a way to fix using Self you'll get

error[E0434]: can't capture dynamic environment in a fn item
  --> src/main.rs:18:64
   |
18 |             static ref RET: Obj<'static> = Obj::from_somewhere(arg)
   |                                                                ^^^
   |
   = help: use the `|| { ... }` closure form instead

when lazy_static expands everything is inside Deref's deref fn so there is no way around this capture problem that I can think of.

@DevinR528

lol.

I didn't notice because ARG is generic (a trait param). And in the couple of cases where I did it manually ARG/arg is ().

I just turned arg into a &str it shouldn't make a difference what type it is as long as its not generic (Self, T, ect). To use lazy_static with a fn call it has to be an argument-less fn like HashMap::new() because of the way the macro expands. playground

Not sure if this will be helpful but here is the expanded version

lazy_static! doesn't apply here because a static is one object, but Self::from_somewhere returns a new object every time it's called.

If you just want to allocate memory and leak it, that's easy enough:

pub fn get_static(arg: &ARG) -> &'static Self {
    Box::leak(Box::new(Self::from_somewhere(arg).expect("...")))
}

But this will return a newly allocated &'static Self every time you call get_static; it won't return a reference to the same one.

The semantics of lazy_static!, if they did work here, suggest that you want to store whatever value was calculated the first time get_static was called, and all subsequent calls to get_static would return references to that value, ignoring arg. Is that what you want? I think you could do that in basically the same way lazy_static works, basically using an Option that is lazily initialized and then using unsafe to create a &'static to the inside.

You can even handle generics, with a different value for each type, using typemap (but only for types that are 'static -- lifetimes are a compile time construct and typemaps work at run time).

Or do you want get_static to look up arg in a table of some kind and return a different &'static Self for each value of arg, but the same reference if you call it with the same arg twice?

2 Likes

I don't know what you mean by "igoring arg", but otherwise, yes, that's what I want.

I think you could do that in basically the same way lazy_static works, basically using an Option that is lazily initialized and then using unsafe to create a &'static to the inside.

An example would be appreciated.

You can use once_cell's OnceCell::get_or_init to do this on a per value basis.

2 Likes

Meaning, if you call get_static("foo") and then get_static("bar"), only "foo" will ever be passed to Self::from_somewhere because the second get_static call will just return a reference to the one that was created last time. This isn't necessarily wrong, but it seems pretty weird, to me -- what's the point of passing an argument every time if it only matters once? Not to mention, if you add threads it also becomes racy. :grimacing: Ugh.

Sorry, I tried, but failed. Knowing the theory does not in this case translate to knowing how to write the code. Maybe I'll give it another shot when I get a chance; I haven't used OnceCell before.

Thanks. I haven't tried it yet. But it looks like the crate could be useful.

Oh, right. That's not an issue. arg does not change for the same type. It exists so default impls can be written in the trait definition.