Why Go And Rust Languages Abandon Inheritance

Hello everyone, I am Hu Ge. Today, let’s talk about why Go and Rust languages have given up inheritance. This topic may seem dull, but there are many interesting technical thoughts behind it, and even some “old jokes” among programmers to share.

Without further ado, let’s get straight to the point.🐯

Why Go And Rust Languages Abandon Inheritance

First of all, we must admit that Object-Oriented Programming (OOP) is not a novel concept anymore. The well-known languages like Java and C++ basically rely on inheritance to organize code.

But have you noticed that as our codebase grows, inheritance often brings us a lot of trouble? Sometimes, to modify a feature, you have to trace back from the subclass all the way to the ancestor class, making you feel like Sherlock Holmes solving a case.😂

The Choice of Go and Rust: Say Goodbye to Inheritance, Embrace Composition

Why have these two languages, Go and Rust, decisively abandoned inheritance? It’s not because they “despise” object-oriented programming, but because they chose a more flexible way to solve problems—composition. You might ask, what is composition? No rush, let’s discuss it step by step.

The problem with inheritance is that although it feels great at first and code reuse is easy and enjoyable, it can lead to many potential troubles, especially when the system becomes complex. Inheritance is like running a chain restaurant; although there are unified standards, once one branch wants to add some unique dishes, you might have to integrate the menu, and the result becomes more and more chaotic.🐔🦆

Let’s consider a classic example: we have a duck and a chicken, and we find that they can both fly and have two legs and two wings. So we abstracted a parent class called “Bird” that both the chicken and the duck inherit from. At first glance, it seems perfect—code reuse and clear logic. But when we continue to expand our business—here comes the penguin. Oh no, this is a big problem; penguins can’t fly; they are just swimming birds. You can’t let penguins inherit from flying birds, can you?🐧

So, you have to continue to break it down and create subclasses of birds: “Flying Bird” and “Non-Flying Bird”. Later on, a rubber duck comes along, which not only can’t fly but isn’t even a real bird. At this point, your design begins to distort, and everyone starts debating what exactly is a bird? What should birds be able to do?

Composition over Inheritance: Flexible and Powerful

At this point, the designers of Go and Rust couldn’t stand it anymore. They believe that instead of dealing with a complex inheritance system, it’s better to keep it simple and use composition to solve the problem. The duck has wings, can fly, and can quack—these traits are directly combined into the duck object, without needing to inherit the entire “bird family” structure. As for the penguin? We just won’t add the “flying” trait; the penguin only needs the ability to “swim”.

In Go and Rust, this method of composition makes the code more flexible and easier to maintain. For example:

Composition in Go

package main

import "fmt"

// Define Flyable interface
type Flyable interface {
    Fly()
}

// Chicken class
type Chicken struct {}

func (c Chicken) Fly() {
    fmt.Println("The chicken is flying!")
}

// Penguin class
type Penguin struct {}

func (p Penguin) Swim() {
    fmt.Println("The penguin is swimming!")
}

func main() {
    var chicken Chicken
    var penguin Penguin
    
    chicken.Fly()    // The chicken is flying
    penguin.Swim()   // The penguin is swimming
}

In the above Go code, we used the composition pattern; the chicken has flying capability while the penguin does not. Each object only contains the behaviors it needs without being forced to inherit a bunch of unrelated things. This way, your code becomes more flexible, allowing you to “compose” functionalities based on actual needs.

Composition in Rust

Rust also adopts a similar approach, using “traits” to achieve composition. Let’s take a look at the code:

trait Flyable {
    fn fly(&self);
}

struct Chicken;

impl Flyable for Chicken {
    fn fly(&self) {
        println!("The chicken is flying!");
    }
}

struct Penguin;

impl Penguin {
    fn swim(&self) {
        println!("The penguin is swimming!");
    }
}

fn main() {
    let chicken = Chicken;
    let penguin = Penguin;

    chicken.fly();   // The chicken is flying
    penguin.swim();  // The penguin is swimming
}

In Rust, the chicken implements the Flyable trait, while the penguin only needs to implement its own “swimming” functionality. Each struct only “composes” the functionalities it needs, making Rust code equally flexible and powerful.

Not only flexibility, but avoiding inheritance in Go and Rust also brings several other benefits:

1. Avoiding the Complexity of Multiple Inheritance: Multiple inheritance often leads to the “diamond problem”, where two parent classes have the same ancestor class, causing the inheritance chain to become chaotic. This is a disaster in C++. By using composition, you only need to assign the functionalities that the object needs, avoiding these complex inheritance chains.

2. Enhanced Code Maintainability: Modifying an inheritance system often causes a chain reaction; a change in one parent class may affect the entire inheritance chain. However, composition allows you to adjust the behavior of a particular object without affecting other parts.

3. A More Natural Modeling Approach: Inheritance is a modeling approach that starts from “what it is”, like “What is a chicken? It is a bird.” But in real life, we often care more about “what it can do”. Go and Rust allow us to focus on the behavior of objects through composition rather than forcing categorization.
The designers of Go and Rust don’t dislike OOP; they just feel that inheritance can sometimes be overly complex and rigid. In these languages, composition allows us to express the behaviors and characteristics of objects more naturally and flexibly, avoiding the complexities and headaches that come with inheritance.
Moreover, their code is easier to maintain and better adapts to future changes.

So, while inheritance has its value, sometimes we need to stop and ask ourselves: “Are we implementing requirements, or are we trying to design a perfect inheritance tree?”🧐

Finally, everyone is welcome to discuss and see whether you prefer inheritance or composition in your development process!

Currently, students interested in programming and the workplace can contact me on WeChat: golang404, and I will add you to the “Programmer Group Chat”.

🔥 Hu Ge’s Private Collection of Quality Recommendations 🔥

As an experienced coder, Hu Ge has organized the most comprehensive“GO Backend Development Resource Collection”.

The resources include “IDEA Video Tutorial”, “The Most Comprehensive GO Interview Question Bank”, “The Most Comprehensive Project Practice Source Code and Videos”, and “Graduation Project System Source Code”, totaling up to650GB. All of it is available for free! It fully meets the learning needs of programmers at various stages!

Leave a Comment