Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Here's my take on implementing this in rust. I made a trait for fetching metadata, that can be implemented by Image, Video, Document, etc:

    trait MetadataSource {
        fn fetch_metadata(&self) -> Metadata;
    }
    impl MetadataSource for Image { ... } 
    impl MetadataSource for Video { ... } 
    impl MetadataSource for Document { ... }
And a separate object which stores an image / video / document alongside its cached metadata:

    struct ThingWithMetadata<T> {
        obj: T, // Assuming you need to store this too?
        metadata: Option<Metadata>
    }

    impl<T: MetadataSource> ThingWithMetadata {

        fn get_metadata(&self) -> &Metadata {
            if self.metadata.is_none() {
                self.metadata = Some(self.obj.fetch_metadata());
            }
            self.metadata.as_ref().unwrap()
        }
    }
Its not the most beautiful thing in the world, but it works. And it'd be easy enough to add more methods, behaviour and state to those metadata sources if you want. (Eg if you want Image to actually load / store an image or something.)

In this case, it might be even simpler if you made Image / Video / Document into an enum. Then fetch_metadata could be a regular function with a match expression (switch statement).

If you want to be tricky, you could even make struct ThingWithMetadata also implement MetadataSource. If you do that, you can mix and match cached and uncached metadata sources without the consumer needing to know the difference.

https://play.rust-lang.org/?version=stable&mode=debug&editio...




Isn't this essentially the generic typestate pattern in Rust? In my view there is a pretty obvious connection between that particular pattern and how other languages implement OO inheritance, though in all fairness I don't think that connection is generally acknowledged.

(For one thing, it's quite obvious to see that the pattern itself is rather anti-modular, and the ways generic typestate is used are also quite divergent from the usual style of inheritance-heavy OO design.)


When you call myImageInstance.fetchMetadata, what does it do? I don't know rust, so it's not clear to me how the value gets cached.


In this example, ThingWithMetadata does the caching. image.fetch_metadata fetches the image and returns it. It’s up to the caller (in ThingWithMetadata) to cache the returned value.


But part of the goal is to not need the caller to cache it. Nor have the class that knows how to fetch it need to know how to cache it either. The responsibility of knowing how to cache the value is (desired to be) in the MetadataSource interface.


The rule is that you can't cache a value in an interface, because interfaces don't store data. You need to cache a value in a struct somewhere. This implementation wraps items (like images) in another struct which stores the image, and also caches the metadata. Thats the point of ThingWithMetadata. Maybe it should instead be called WithCachedMetadata. Eg, WithCachedMetadata<Image>.

You can pass WithCachedMetadata around, and consumers don't need to understand any of the implementation details. They just ask for the metadata and it'll fetch it lazily. But it is definitely more awkward than inheritance, because the image struct is wrapped.

As I said, there's other ways to approach it - but I suspect in this case, using inheritance as a stand-in for a class extension / mixin is probably going to always be your most favorite option. A better approach might be for each item to simply know the URL to their metadata. And then get your net code to handle caching on behalf of the whole program.

It sounds like you really want to use mixins for this - and you're proposing inheritance as a way to do it. The part of me which knows ruby, obj-c and swift agrees with you. I like this weird hacky use of inheritance to actually do class mixins / extensions.

The javascript / typescript programmer in me would do it using closures instead:

    function lazyResource(url) {
      let cached = null
      return async () => {
        if (cached == null) cached = await fetch(url)
        return cached
      }
    }

    // ...
    const image = {
      metadata: lazyResource(url)
    }
Of all the answers, I think this is actually my favorite solution. Its probably the most clear, simple and expressive way to solve the problem.


> The rule is that you can't cache a value in an interface, because interfaces don't store data.

Right, but the start of where I jumped into this thread was about the fact that there are places where fields would make things better (specifically in relation to traits, but interfaces, too). And then proceeding to discuss a specific use case for that.

> A better approach might be for each item to simply know the URL to their metadata.

Not everything is a coming from a url and, even when it is, it's not always a GET/REST fetch.

> but I suspect in this case, using inheritance as a stand-in for a class extension / mixin is probably going to always be your most favorite option

Honestly, I'd like to see Java implement something like a mixin that allows adding functionality to a class, so the class can say "I am a type of HasAuthor" and everything else just happens automatically.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: