The simplest traits showcase
Tags: rust, java
Published: 2022-08-22 23:39:36

## Intro
It is handy to be able to separate the interface from the implementation; however, it often is not enough. In this piece, I'd like to talk about probably the simplest example of an interface-class paradigm limitation.

What if we had to design a `Size` class in Java or other classic OOP language? There are many variations, especially in construction (factories) and setters (if any), but one thing would have remained unchanged in every single implementation: the two numbers.

```swift
// Java

class Size {
    private int mWidth;
    private int mHeight;
    
    public Size(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException();
        }
        mWidth = width;
        mHeight = height;
    }

    public int width() {
        return mWidth;
    }

    public int height() {
        return mHeight;
    }

    public int area() {
        return width() * height();
    }
}
```

Every engineer had to write something like this a couple of times, but has this never bothered you from the data perspective? It strikes me odd every time a pair of numbers is suddenly a `Size`. A pair of numbers can be anything, but since the data is not detachable from implementation, we create classes like `Size`, `Point`, and `ComplexNumber` – all containing precisely two numbers: from a data perspective, they are not different types.

Of course, we do this because, in Java, we have to think from the interface's perspective; implementation simply contains all the necessary member variables to fulfil the interface contract. The price we pay here is having to create a `Size` even if we have a pair of perfectly fine numbers on the stack.
In the case of `Size`, it does not seem like a big sacrifice; however, once the structure of the types, the transformations performed on them, and the wiring become more complex, not being able to use perfectly fine data can become rather limiting, especially if it's be something big.

## The solution
What if there was an idiomatic way to treat data in different ways on different occasions? This is precisely what a trait is: a predefined way to interpret certain data type. Let us have a look at how similar thing could be done with traits in Rust.

First, we don't even have to declare a data type here: we can simply use a tuple instead. Here is how we'd declare a variable suitable to be treated as a size.
```rust
// Rust

let s = (2, 3);
```

Then we declare a trait. Notice that we define `area()` function right away. That's because the area calculation does not depend on the details of `width()` and `height()` functions implementation details.

```rust
// Rust

trait Size {
    fn width(&self) -> u32;

    fn height(&self) -> u32;

    fn area(&self) -> u32 {
        return self.width() * self.height();
    }
}
```

All that is left to do is to define to interpret a tuple of two integers `(u32, u32)` as a `Size`.
```rust
// Rust

impl Size for (u32, u32) {
    fn width(&self) -> u32 {
        return self.0;
    }

    fn height(&self) -> u32 {
        return self.1;
    }
}
```

It is just as simple to implement the `Size` trait for a slice of two `u32`'s.
```rust
// Rust

impl Size for [u32; 2] {
    fn width(&self) -> u32 {
        return self[0];
    }

    fn height(&self) -> u32 {
        return self[1];
    }
}
```

We have just implemented `Size` trait for two basic data types which preexisted in the language. Now let us try invoking these implementations.
```rust
// Rust

fn foo() {
    let tuple = (2, 3);

    // Automatic trait resolution
    println!("Area: {}", tuple.area());
    
    // Explicitly choose trait implementation
    println!("Area: {}", Size::area(&tuple));

    
    let array = [4, 5];

    // Automatic trait resolution
    println!("Area: {}", array.area());
    
    // Explicitly choose trait implementation
    println!("Area: {}", Size::area(&array));
}
```

## Thoughts
Data doesn't have to be glued together with the implementation even when the interface is declared separately, and creating useful code without defining redundant data types is possible. With traits, we can interpret the same piece of data differently on different occasions, which changes everything in the pursuit of a _good design_. Traits are sometimes referred to as a concept of object-oriented programming, but in my opinion it is misleading: the complete traits can only be found in languages leaning towards data-oriented design, like Rust.