std::marker::PhantomData and unused fields in structs

It really isn't :wink: Sure, at first glance it seems that requiring an extra unused field just to be able to carry a type parameter looks cumbersome and unneeded, but it turns out that is paramount to have such a construction as soon as the language offers subtyping and variance. These are quite advanced topics, on which you may find (my) one-post-explanation here: Looking for a deeper understanding of PhantomData - #4 by Yandros

The other option would have been not to have any variance whatsoever, and that would have been incredibly cumbersome. So it is a lesser inconvenience.

That being said, in your case you could get away without PhantomData, if you allowed "invalid" unusable Cachers to be constructed, by moving the bounds onto the get() function:

It may look simpler for the library writer, but imho having the Fn bound since the beginning like you did is way healthier for a user of the library :slight_smile:


Finally, here are a few tips:

  1. You can loosen V : Copy to V : Clone, and use an explicit .clone() call

  2. You can loosen F : Fn... to F : FnOnce since you will only be calling the function once. This gets tricky since you need owned access to the closure field to call an FnOnce, but your .get() method takes a self receiver by unique reference (&'_ mut Self). The trick to go from &mut T to T is to mem::replace the value with some arbitrary default sentinel, and in Rust the type that lets you do that in a very idiomatic way is Option, and its .take() method.

  3. Option offers a neat method / idiom for the "match an Option and on None compute" pattern: .get_or_insert_with

    pub
    struct Cacher<F, U, V>
    where
        F : FnOnce(U) -> V,
        V : Clone,
    {
        calculation: Option<F>,
        value: Option<V>,
        _phantom: ::std::marker::PhantomData<U>,
    }
    
    impl<F, U, V> Cacher<F, U, V>
    where
        F : FnOnce(U) -> V,
        V : Clone,
    {
        pub
        fn new (calculation: F) -> Cacher<F, U, V>
        {
            Cacher {
                calculation: Some(calculation),
                value: None,
                _phantom: Default::default(),
            }
        }
    
        pub
        fn get (self: &'_ mut Self, arg: U) -> V
        {
            let calculation = &mut self.calculation;
            self.value
                .get_or_insert_with(|| {
                    calculation.take().unwrap()(arg)
                })
                .clone()
        }
    }
    

The last one is just to show an example of the utilities present in Rust most pervasive data structures: know that the version with the match is just as good :slight_smile:

1 Like