Walking through "The Java Tutorials" with Rust

Reading 'What Is an Interface?' and trying Rust's impl specialization

A terribly hot evening in July 2021

As we mentioned in the previous post, Java interfaces have some advantages over class inheritance - to name one, a Java class can implement any number of interfaces. This lesson in the java tutorials is describing what is an interface and demonstrates defining and implementing a Bicycle interface.

Lesson link: What Is an Interface?

Java's Interfaces and Rust's Traits

Rust traits and java interfaces are conceptually close:

  • Both define the behavior a struct/class should implement.
  • There are compile type checks that call sites that expect specific trait/interface would allow only these objects.

Rust traits - a short recap

We showed an example of a Bicycle trait and Basic/MountainBicycle structs that implement it in our first attempt to mimic inheritance with traits:

  • The code there was quite similar to the java example the current lesson
trait Bicycle { // java: interface Bicycle
   ...

impl Bicycle for BasicBicycle { // java: class BasicBicycle implements Bicycle
   ...

impl Bicycle for MountainBicycle { // java: class MountainBicycle implements Bicycle
   ...

Coding for an interface

Suppose we don't need runtime polymorphism or to force a superclass-subclass model, Rust provides other mechanisms that allow "coding for an interface" i.e writing generic code that can operate on different concrete types that implement specific trait/s.

For example, suppose we define a take_a_ride() method on Bicycles and would like to have a default implementation for all Bicycle objects. I can think of two rust statements I can use:

  • Generic impl block (which I thought was a great idea, and felt natural to write)
  • Trait default implementation.

Generic impl block bounded by the Bicycle trait

Define a Ridable trait, and provide a generic implementation for every object that implements the Bicycle trait

trait Ridable {
    fn take_a_ride(&mut self);
}

impl<T> Ridable for T where T : Bicycle { 
    fn take_a_ride(&mut self){
        &self.speed_up(10);
    }
}
// Caveat: this will collide with other impl Ridable that happen to match Bicycle

// ...

#[test]
fn take_a_ride_test(){
    let mut bike = BasicBicycle::default();
    bike.take_a_ride();
    assert!(bike.get_speed() == 10);
}

Note that in current stable rust (version 1.5.3), looks like rust doesn't allow to define multiple impl blocks that can match a specific instance. For example, suppose we define a Special trait, mark MountainBicycle as special, and would like to override the above Ridable impl block just for Special Bicycles:

trait Special : Bicycle {}
impl Special for MountainBicycle {}

// This impl block won't compile
impl<T> Ridable for T where T : Special{
    fn take_a_ride(&mut self){
        &self.speed_up(3);
    }
}

// Compiler would complain about "conflicting implementation"
35 | impl<T : Bicycle> Ridable for T{
   | ------------------------------- first implementation here
...
41 | impl<T> Ridable for T where T : Special{
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

After some searching, I've found that this was proposed a few years ago (see RFC: impl specialization, and Tracking issue for specialization) and called "specialization". There is already a feature flag that I can use to partially enable it in rust nightly called min_specialization. It enables overriding a generic impl block for specific types (but not traits), so using min_specialization and rust nightly we can write:

#![feature(min_specialization)]

impl<T : Bicycle> Ridable for T{
    default fn take_a_ride(&mut self){
        &self.speed_up(10);
    }
}

impl Ridable for MountainBicycle {
    fn take_a_ride(&mut self){
        &self.speed_up(3);
    }
}

BTW - changing to rust nightly is quite simple

# install nightly toolchain
$ rustup toolchain install nightly 

# set rustup to use nightly in a specific directory
$ cd to_the_folder_where_I_want_nighly
$ rustup override set nightly

Note to self - learn about rust/functional alternatives to Java's approach

Both inheritance and specialization (at least, overriding implementations in some hierarchy of interfaces) are models that I used to have in Java. I found that even though they are extensively discussed in various rust-lang RFCs, there are still design gaps and tradeoffs to consider before they could be available in rust. Some of the gaps stem from rust design goals that Java doesn't share, but probably because there are alternatives in many situations (which I don't know about yet, so please drop a hint if you reached here. The rust book gives a taste of this in the chapter Implementing an Object-Oriented Design Pattern, and I suppose I can learn things from Scala programs that share some functional characteristics with Rust).

Trait default implementation

Rust provides a notion of Subtraits which adds more knowledge about the implementing type (I recommended reading the tour of Rust's standard library traits, see for example the section about Subtraits and Supertraits). Using it we can define a new trait that's available for types that implement the Bicycle trait. Like any other trait, we can provide a default implementation for types that are marked as implementing the Ridable trait and "override" it for specific types.

trait Ridable : Bicycle { // Ridable is a new trait available for types that implement Bicycle
    fn take_a_ride(&mut self) { // default implementation
        &self.speed_up(10);
    }
}

impl Ridable for BasicBicycle {} 
// should explicitly opt in to make BasicBicycle 'Ridable' and have the default implementation. 
// Also, I wish I could end this line with `;` and not an empty block `{}` :-)

// provide different impl for MountainBicycle 
impl Ridable for MountainBicycle {
    fn take_a_ride(&mut self){
        &self.speed_up(3);
    }
}

What I'm missing?

Trying to compile the example in this post surprised me a bit, and I'm left with the feeling that I'm missing something important. I'll edit this post later with links to new findings. Moving on for now.