From PHP to Go

Besides being very powerful, Go is a clean language. It was easy to get started with it, despite it has some obvious major differences if coming from a language like PHP. I knew some of them, cause they’re specific to any compiled language, while in an interpreted one you have to work in order to get their benefits.

I got comfortable with them and even wished PHP had them. I’m not comparing the two languages by considering one to be better or worse, I’m only telling some differences that caught my eye, even if they are normal to be.

It got me happy about writing code in a very different way, putting aside some things that are normal in other contexts and I was used to.

Strong and static types

Types in PHP can be sometimes looked at as being an architectural choice. You can choose type hinting, though it should be against the law not to. It’s a struggle to convince other people it’s very important to know the type of data you’re working with. In Go it’s all there, you can’t choose another way to do it. Of course, you can pass on arbitrary data with the empty interface{}, but when you want to use it, you need an assertion to a specific type.

Another aspect I like is that you can’t just return null (nil) if there is no value to return (except for errors).

I’m using an RPC server these days, and I have an integer argument for one of the methods. The value I’m expecting needs to be unsigned, so I’ve declared it as uint and I don’t need any extra data validation.

Multiple return values

If you need to return multiple values in PHP, you can choose the ugly way, returning an array, or the clean one, returning a known object. In Go it’s built-in:

func MyFunc(a, b int) (int, int) {
       return a + b, a -b
}

x, y := MyFunc(1, 2)

Interfaces

They’re a bit magically in Go, because you don’t tell a struct to implement an interface. If it implements all the interfaces’ methods, it’s automatically considered it implements that interface.

package main

import "log"

type JobRunner interface {
       Run() error
}

type LocalJobRunner struct {
}

func (ljr *LocalJobRunner) Run() error {
       log.Println("Local")
       return nil
}

type RemoteJobRunner struct {
}

func (rjr *RemoteJobRunner) Run() error {
       log.Println("Remote")
       return nil
}

func RunJob(jr JobRunner) {
       jr.Run()
}

func main() {
       LocalJobRunner := &LocalJobRunner{}
       RunJob(LocalJobRunner)

       RemoteJobRunner := &RemoteJobRunner{}
       RunJob(RemoteJobRunner)
}

Inheritance

No. Not in a MyClass extends YourClass way. It’s called embedded types, and it translates as:

package main

type LocalJobRunner struct {
       Name string
}

type RemoteJobRunner struct {
       LocalJobRunner
       URL string
}

func main() {
       runner := RemoteJobRunner{}
       runner.Name = ""
       runner.URL = ""
}

Read here a reason why this is considered better.

Exceptions

You can’t throw an exception and expect someone to catch it. Instead, it’s everything about errors being returned by your functions and carefully treated. It’s very common to use multiple return values for this situation.

package main

import (
       "errors"
       "fmt"
       "log"
)

func RunJob(level int) (int, error) {
       if level > 1 {
              return 0, errors.New(fmt.Sprintf("You cannot run a level %d job", level))
       }
       
       // run job
       
       return 23, nil
}

func main() {
       id, err := RunJob(3)
       
       if err != nil {
              log.Fatal(err)
       }
       
       log.Println(id)
}

Beware of not getting lost among the evil if statements. It took me a while to understand you don’t check errors at every step, but you pass them until the best moment to take action.

It’s all about precision.

Leave a Reply

Your email address will not be published. Required fields are marked *