DEV Community

Cover image for Learning Go as a Ruby Developer #3: Functions, Methods, and Why Go Doesn't Have Classes
Shrouk Abozeid
Shrouk Abozeid

Posted on

Learning Go as a Ruby Developer #3: Functions, Methods, and Why Go Doesn't Have Classes

One of the biggest mindset shifts I've had while learning Go came when I reached functions and methods.

As a Ruby developer, almost everything revolves around objects. Every piece of behavior belongs to a class, and every function is really just a method attached to an object.

Go takes a different approach.

Functions exist independently, and methods are simply functions with a receiver. There are no classes, no inheritance, but still writing object-oriented code.

Let's see how Go approaches behavior compared to Ruby.


Why This Felt Strange Coming from Ruby

In Ruby, we rarely think about standalone functions because they don't really exist.

Everything is an object.

class Calculator
  def add(a, b)
    a + b
  end
end

calculator = Calculator.new
calculator.add(2, 3)
Enter fullscreen mode Exit fullscreen mode

Want behavior?

Create a class.

Need reusable logic?

Another class or a module.

Go starts from a much simpler place.

Most behavior begins as just... a function.


Functions in Go

Functions are declared using the func keyword.

func add(a int, b int) int {
    return a + b
}
Enter fullscreen mode Exit fullscreen mode

The syntax may look unusual at first, especially if you've spent years writing Ruby.

Instead of writing the return type first, Go places it after the parameter list.

Calling the function is exactly what you'd expect.

result := add(2, 3)
Enter fullscreen mode Exit fullscreen mode

Grouping Parameters

Go lets you group parameters that share the same type.

Instead of writing:

func add(a int, b int) int
Enter fullscreen mode Exit fullscreen mode

you can write:

func add(a, b int) int {
    return a + b
}
Enter fullscreen mode Exit fullscreen mode

It's a small feature, but it keeps function declarations much cleaner.


Returning Multiple Values

func divide(a, b int) (int, int) {
    return a / b, a % b
}
Enter fullscreen mode Exit fullscreen mode

Then you can capture both results.

quotient, remainder := divide(10, 3)
Enter fullscreen mode Exit fullscreen mode

Coming from Ruby, this felt familiar because Ruby also supports multiple assignment.

def divide(a, b)
  [a / b, a % b]
end

quotient, remainder = divide(10, 3)
Enter fullscreen mode Exit fullscreen mode

The difference is that in Ruby we're really returning an array, while in Go multiple return values are part of the language itself.


Named Return Values

One feature I wasn't expecting was named return values.

Instead of writing:

func sum(a, b int) int {
    return a + b
}
Enter fullscreen mode Exit fullscreen mode

you can write:

func sum(a, b int) (result int) {
    result = a + b
    return
}
Enter fullscreen mode Exit fullscreen mode

Notice the bare return.

Because result was named in the function signature, Go automatically returns it.

This is called a naked return.


Anonymous Functions

Go also supports anonymous functions.

You can assign them to variables.

greet := func(name string) {
    fmt.Println("Hello", name)
}
Enter fullscreen mode Exit fullscreen mode

Or execute them immediately.

func() {
    fmt.Println("Running immediately")
}()
Enter fullscreen mode Exit fullscreen mode

This reminded me a lot of Ruby blocks and lambdas.

greet = ->(name) { puts "Hello #{name}" }

greet.call("Alice")
Enter fullscreen mode Exit fullscreen mode

Although the syntax is different, the concept feels familiar.


Variadic Functions

Sometimes you don't know how many arguments a function should accept.

Go solves this using variadic parameters.

func printNumbers(numbers ...int) {
}
Enter fullscreen mode Exit fullscreen mode

The ... tells Go to collect all arguments into a slice.

Ruby has something very similar.

def print_numbers(*numbers)
end
Enter fullscreen mode Exit fullscreen mode

This was one of the easiest concepts for me to understand because the two languages approach it almost the same way.


So Where Are the Classes?

This was the question I kept asking while reading through the documentation.

If Go doesn't have classes...

Where do methods live?

The answer surprised me.

Methods belong to types, not classes.


Methods in Go

A method is simply a function with a receiver.

type Person struct {
    Name string
}

func (p Person) SayHello() {
    fmt.Println("Hello,", p.Name)
}
Enter fullscreen mode Exit fullscreen mode

Now we can call it like this.

person := Person{Name: "Alice"}
person.SayHello()
Enter fullscreen mode Exit fullscreen mode

The syntax felt unusual at first.

Instead of defining the method inside the type like Ruby does, the receiver appears before the method name.

func (receiver Type) MethodName() {
}
Enter fullscreen mode Exit fullscreen mode

Value Receivers

By default, methods receive a copy of the value.

func (p Person) Rename(name string) {
    p.Name = name
}
Enter fullscreen mode Exit fullscreen mode

Changing p.Name only changes the copy.

The original object stays exactly the same.

This is called a value receiver.


Pointer Receivers

If you want to modify the original value, you use a pointer receiver.

func (p *Person) Rename(name string) {
    p.Name = name
}
Enter fullscreen mode Exit fullscreen mode

Now changes affect the original struct.

This distinction between value and pointer receivers was one of the first places where Go reminded me of C++.

Ruby developers usually don't think about this because objects are already passed around by reference-like semantics.

Go asks you to decide whether you're working with a copy or the original value.


Side-by-Side Comparison

Go Ruby
Standalone functions Everything is a method
func keyword def keyword
Multiple return values Usually arrays or multiple assignment
Anonymous functions Lambdas and Procs
Variadic parameters (...) Splat operator (*)
Methods attached to types Methods defined inside classes
Value receivers Objects usually mutated directly
Pointer receivers Reference-like object semantics

Thoughts

Ruby's object model is incredibly elegant.

Everything feels consistent because everything is an object.

Go takes a more lightweight approach.

Instead of encouraging you to wrap every behavior inside a class, it asks a simple question:

Does this really need to belong to a type?

Sometimes the answer is yesβ€”a method makes sense.

Other times, a standalone function is perfectly fine.

Go is trying to remove the parts it considers unnecessary while keeping the parts that are genuinely useful.

This has been one of the most interesting lessons so far in my Go journey, what about you?

Top comments (0)