The public methods add, remove, and average are the only way to modify an instance of AveragedCollection. Because we’ve encapsulated the implementation details of AveragedCollection, we can easily change aspects like the data structure in the future.
As long as the signatures of the add, remove, and average public methods stay the same, code using AveragedCollection wouldn’t need to change. If we made list public instead, this wouldn’t necessarily be the case: Hash Set and DEC have different methods for adding and removing items, so the external code would likely have to change if it was modifying list directly.
The option to use pub or not for different parts of code enables encapsulation of implementation details. Rust code can be shared using default trait method implementations instead, which we saw in Listing 10-15 when we added a default implementation of the summary method on the Summarizable trait.
Any type implementing the Summarizable trait would have the summary method available on it without any further code. This is also called polymorphism, which means that multiple objects can be substituted for each other at runtime if they share certain characteristics.
But it’s actually a more general concept that refers to code that can work with data of multiple types. Subclasses shouldn’t always share all characteristics of their parent class, but will do so with inheritance.
This can make a program’s design less flexible, and introduces the possibility of calling methods on subclasses that don’t make sense or that cause errors because the methods don’t actually apply to the subclass. Some languages will also only allow a subclass to inherit from one class, further restricting the flexibility of a program’s design.
For these reasons, Rust chose to take a different approach, using trait objects instead of inheritance. Image by the author you’ve heard about Rust, then you probably know why people are so excited about it.
Memory safety through the ownership and borrowing system, blazing-fast speed, first-class support for Web Assembly, and so on. A lot of programmers aren’t doing things that really benefit from Rust ’s most lucrative advancements, however, the choices that went into designing this programming language make it worth exploring.
I think for intermediate programmers, working with and understanding the design of Rust can strengthen your programming intuition. It’s a great way that the programming language itself follows DRY principles, and I think having the struct feature double in this way makes a lot of sense.
Rust actually figured people might be using this a bit more often and implemented a better version of the switch case, match, to make for better control flow. Although the ENIM somewhat supports the sort of general, parent object type, its primary purpose is to embody variants.
Going off of the first example, you can implement methods for ends, but when we want a model for more general interfacing, we have to turn to something else. This is an example of where you can pass in a type that implements the trait as a parameter, similar to how you would with an interface in other languages.
Drawing connections between programming languages, and seeing where the purposes of features overlap can help give us as programmers a better understanding of the tools we use so often. Rust is special because of the different design choices its developers made.
Although it doesn’t cover all the nuances, it supports an elegant system that makes sense, and fulfills all the necessary purposes, albeit differently. Medium is an open platform where 170 million readers come to find insightful and dynamic thinking.
Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox.
Many folks think of Rust as a functional language, and while there are plenty of functional paradigms in Rust, many of those paradigms are also available to other languages in one way or another. Most folks would not call Java a functional language, and yet many of the features cited that make Rust a functional language are available as libraries for Java.
If you want algebraic data types, there’s a library for that. The absence or presence of such features does not make a language object oriented or functional, there are plenty of ways to stamp features from one language onto others.
You don’t have to leave the standard library to access them either! The remainder of this post will talk about OOP in the context of Rust itself, and common pitfalls you may hit along the way while writing objectorientedRust or using object oriented patterns.
Rust has a concept of traits, which are the Java equivalent of interfaces. They define a common set of methods to be used across object types.
SoftwareEngineer is a Portrait that encapsulates the functionality of Worker. First we are making some structs to hold traits for the implements.
Despite not needing it, you’ll note I made the pay_worker method take ownership of the Worker argument. This is the simplest and fastest way to pass generic objects around.
Rust knows ahead of time what your type is and just calls the method. I want to get a bunch of these workers, put them in a collection, and give them all pay.
All imply Trait s must eventually compile down to the same type, meaning there is no way to carry a collection of traits using static dispatch if they compile to different implementations. This is one of the most frustrating behaviors of static dispatch, as it makes it very difficult to pass ownership of objects around in a generic way.
Heap or stack allocated, method calls must be behind a pointer to the trait, and rust will figure out what to do from there. The only “real” change to the code was from imply Dan This has performance implications, but also opens up a world of possibilities.
We’re still using references here, but if we wrapped our astronaut and engineer in a Box, we would own them in the DEC. Impl is syntactic sugar for a more verbose syntax that specifies data is generic over a type.
For some reason the imply syntactic sugar does not work here. There isn’t much more excitement to talk about for static dispatch here, so let’s move back to dynamic to discuss a few other quirks with it.
This is the proper syntax for holding a trait using dynamic dispatch: This method wants an instance of the astronaut, and so we comply.
It thinks we’re going to take upsized data and try to put it on the stack which rust cannot and will not handle. In order to clone a box (or rather, what we’d really like is: to clone what is inside the box, allocate new memory in the heap for it, and return that new box), we must venture outside the standard library to a crate called cyclone.
So long as all implements of the trait also implement clone, we may implement cyclone on our trait and clone the data inside the box to another box. Seems like a bit of a hassle, but if you want to easily clone your instances inside boxes, this is more or less the only way.
Depending on the use, we may have been able to get away with using RC or Arc, but sometimes you need ownership. Well it turns out it isn’t too hard, although you have to waste a whole function call on the conversion which is a shame.
The kind folks on Reddit pointed me to a handy crate that hides all this behind a pro macro if you don’t feel like implementing it yourself. Occasionally you may want to add a method to a trait that does not reference self.
The most common case I’ve seen is a constructor, but there are plenty of other reasons you may want to add one. With no self parameter, rust tells us we can’t add the method to our trait.
Since there is no self parameter in the method, rust won’t know what implementation to call. I’ve stuck to my unofficial schedule of one post a month, and all the others talk about something that has to do with rust.