Is Go Object Oriented ?

Friday, March 31, 2023 · 5 minutes · 923 words

During a discussion on a Discord channel, a message puzzled me: Go is not an object-oriented language.

I knew Go didn’t have an inheritance mechanism. But, was that enough to disqualify it as an object language ?

Allow me to share with you my thoughts on the subject.

But first, what is an object language ?

Wikipedia defines an object as a representation of a concept, idea, or entity in the physical world1, with properties and behaviors.

For example, we could represent a car by its brand, color and fuel. That would represent its properties.

For its behavior, we can say that a car can start, accelerate and brake.

In the same entity, we find data to store the states (properties), and functions or methods to interact with the object (behavior).

So, the definition of an object language would be a language that allows data and functions to be grouped together in the same structure?

Not so simple because with the arrival of new languages, or the evolution of some, this definition has evolved. Today, we can define an object language according to the following criteria :

  • Encapsulation: the object has a state (data) and a behavior (methods). We are clearly in the definition of the object seen above.

  • Abstraction: the object can “hide” its internal functioning and expose an interface allowing other objects to interact with it.

  • Inheritance: the object can adopt the behavior of another object without it being necessary to redefine this behavior.

  • Polymorphism: either the same behavior changes according to the object to which it applies, or an inherited behavior is redefined.

Does Go tick all the boxes of an object language ?

Encapsulation

A struct is a collection of data in Go. Here is how I will define a new type ‘Point’ as a composite object containing its position:

type Point struct {
	X, Y float64
}

It is possible to define methods on types thanks to the receiver. We add the type that receives the method before the name of the method. For example :

func (p Point) Dist() float64 {
	return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

The ingredients for encapsulation are all there: a way to bundle data and behaviors.

Abstraction

Data visibility from the outside2 is defined by the case of the variable name: lower case for a private variable and upper case for a public variable. For example :

type Car struct {
    Color string   // accessible from outside
    code  string   // not accessible from outside
}

The same principle is applied for methods which, if their name begins with a lowercase letter, cannot be used externally. Conversely, if their name begins with a capital letter, they will be visible.

Go calls this mechanism the export of names and allows functionality to be hidden or exposed, as defined by the abstraction.

Inheritance

I said above that I knew Go had no inheritance. But that doesn’t necessarily mean that Go doesn’t achieve the same thing as inheritance. Indeed, Go allows type embedding.

For example, let’s take the example of the car and define a “Vehicle” object with a “Move” method:

type Car struct {
    Name string
}
func (v Vehicle) Move() {
    println("I move")
}

Anything that makes up a vehicle can be incorporated into a “Car” object as follows:

type Car struct {
    Vehicle
}

What if we had to use all of this:

var tuture Car 

tuture.Vehicle.Move()  // "I move"
tuture.Move()           // "I move"

Complete example on the playground

The two calls to the “Move” method are valid and there is a way to inherit behaviors from one object to another.

Polymorphism

In the previous example, the 2 calls to the “Move” method gave the same result because they called the same “Move” method defined at the level of the “Vehicle” object. We can redefine the method at the “Car” level as follows:

func (v Car) Move() {
    println("I move like a car")
}

Therefore, calls to “Move” would give different results:

var tuture Car

tuture.Vehicle.Move()    // "I move"
tuture.Move()             // "I move like a car"

A method inherited from a parent object can therefore be modified at the child object level.

But we can go further. Go has the notion of interface which allows us to define types by their behavior. Concretely, an interface is a collection of methods.

type Vehicle interface {
    Move()
}

type Car struct {}
func (v Car) Move() {
    println("I move like a car")
}

type Bus struct {}
func (c Bus) Move() {
    println("I move like a bus")
}

func Check(v Vehicle) {
    v.Move()
}

We could then call “Check” either with a “Car” type or a “Bus” type as in the following example:

var tuture Car
var minibus Bus

Check(tuture)   // "I move like a car"
Check(minibus)  // "I move like a bus"

Complete example on the playground

With the redefinition of inherited methods and interfaces, Go implements polymorphism at several levels.

Conclusion

Go is indeed an object language. So what ?

What matters much more than ticking the boxes of this or that paradigm is giving coders all needed tools to do their job as well as possible. The designers of Go took from other languages what seemed to them the most relevant to make the code readable, efficient and maintainable.

In my opinion, Go remains an extremely productive language in my daily life as a developer.


  1. Wikipedia page on object-oriented languages ↩︎

  2. The notion of exterior must be understood in the sense of packages which is the mechanism for variables and functions scope in Go ↩︎

thinking go