Trying to have a calculated once associated function with no arguments

Traits make it possible to have associated functions, methods, types, and consts for implementing types. I am in a situation where I am deriving a trait for structs, that involves one non-trivial calculation, and the result of that calculation can be used over and over again throughout the binary. I have tried to model this as an associated const, but because of the nature of the calculation, it cannot be done trivially for const (requires some allocation). And even if it could, it would come with drawbacks as outlined here. The other option would be to run the calculation inside an associated function with no arguments, but then it is wasting time redoing the calculation. Is there a way to model this such that the calculation is only done once and then used as the return for the associated function?

Other things considered that I don't love:

  • Global dictionary that uses the struct identity as the key lookup
1 Like

It might help if you could share some example code that shows the shape of the trait you're working with and the computation you want to perform. One possibility that comes to mind is using a wrapper struct with a once_cell::sync::Lazy field, like this:

trait Base {
    // ...
}

fn non_trivial_calculation<T: Base>() -> SomeData {
    // allocations, etc.
}

struct Wrapper<T> {
    memo: Lazy<SomeData>,
    inner: T,
}

impl<T: Base> Wrapper<T> {
    fn new(inner: T) -> Self {
        Self {
            memo: Lazy::new(non_trivial_calculation::<T>), 
            inner,
        }
    }

    fn get(&self) -> &SomeData {
        &*self.memo
    }

    // other methods to access `inner`, presumably
}

That will perform the actual computation for T at most once per instance of Wrapper<T>. To ensure that it happens at most once ever, you would indeed need some kind of map keyed by type to keep track of the memoized results, either a true (static) global or passed around explicitly to functions that need it.

3 Likes

Of course you don't have to package the Lazy together with a value of the implementing type, and maybe the only reason I wrote it that way is to avoid PhantomData… the point is, by looking at where the output of this type-dependent computation is used you might be able to figure out a place to store the Lazy that gives convenient access to the memoized output, and such that you avoid duplicating the computation for a given type without a global data structure.

1 Like

This is great! Thank you so much for responding! I'm going to look into this as a possible solution! I may come back to ask you about PhantomData and Lazy as they are new to me.

A code example almost exactly what Im trying to do is in this stack overflow post.. Which is linked above but for a different reason. The one difference being that their solution can be made into a const function

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.