trait Robot {
fn send_command(&mut self, command: Command);
fn stop(&mut self) {
self.send_command(Command.STOP);
}
}
struct BenderRobot;
impl Robot for BenderRobot {
// Required.
fn send_command(&mut self, command: Command) { todo!(); }
}
This is starting to look a lot like C++ class inheritance. Especially because traits can also inherit from one another. However, there are two important differences: First, traits don't define any fields. And second, BenderRobot is free to implement lots of other traits if it wants, too.If you want a real world example of this, take a look at std::io::Write[1]. The write trait requires implementors to define 2 methods (write(data) and flush()). It then has default implementations of a bunch more methods, using write and flush. For example, write_all(). Implementers can use the default implementations, or override them as needed.
Docs: https://doc.rust-lang.org/std/io/trait.Write.html
Source: https://doc.rust-lang.org/src/std/io/mod.rs.html#1596-1935
How does one handle cases where fields are useful? For example, imagine you have a functionality to go fetch a value and then cache it so that future calls to get that functionality are not required (resource heavy, etc).
// in Java because it's easier for me
public interface hasMetadata {
Metadata getMetadata() {
// this doesn't work because interfaces don't have fields
if (this.cachedMetadata == null) {
this.cachedMetadata = generateMetadata();
}
return this.cachedMetadata;
}
// relies on implementing class to provide
Metadata fetchMetadata();
}It sounds like you could solve that problem in a lot of different ways. For example, you could make an HTTP client wrapper which internally cached responses. Or make a LazyResource struct which does the caching - and use that in all those different types you're making. Or make a generic struct which has the caching logic. The type parameter names the special individual behaviour. Or something else - I don't have enough information to know how I'd approach your problem.
Can you describe a more detailed example of the problem you're imagining? As it is, your requirements sound random and kind of arbitrary.
public interface MetadataSource {
Metadata metadata = null;
default Metadata getMetadata() {
if (metadata == null) {
metadata = fetchMetadata();
}
return metadata;
}
// This can be relatively costly
Metadata fetchMetadata();
}
public class Image implements MetadataSource {
public Metadata fetchMetadata() {
// goes to externally hosted image to fetch metadata
}
}
public class Video implements MetadataSource {
public Metadata fetchMetadata() {
// goes to video hosting service to get metadata
}
}
public class Document implements MetadataSource {
public Metadata fetchMetadata() {
// goes to database to fetch metadata
}
}
Each of the above have completely different ways to fetch their metadata (ex, Title and Creator), and of them has different characteristics related to the cost of getting that data. So, by default, we want the interface to cache the result so that the1. The thing that _has_ the metadata only needs to know how to fetch it when it's asked for (implementation of fetchMetadata), and it doesn't need to worry about the cost of doing so (within limits of course)
2. The things that _use_ the metadata only need to know how to ask for it (getMetadata) and can assume it has minimal cost.
3. Neither one of those needs to know anything about it being cached.
I had a case recently where I needed to check "does this have metadata available" separate from "what is the metadata". And fetching it twice would add load.
class CachedMetadataSource implements MetadataSource {
CachedMetadataSource(MetadataSource uncachedSource) {}
Metadata getMetadata() {
if (metadata == null) {
metadata = uncachedSource.getMetadata();
}
return metadata;
}
} 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...
(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.)
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.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.