Go, Rust and PHP: The 32 differences
I couldn’t resist the temptation to pick Go and see how it’s different from PHP and Rust
After this comment in my last article, I had a mild realization when checking my options: I wasn’t considering the Go language in all of this.
Both languages, Rust and Go, appeared almost at the same time, but their focus is kind of… different. While Rust offers “C++ done right”, exposes manual control almost everywhere and close-to-the-metal performance, Go says that for some tasks you don’t need to go THAT deep as long you’re fine with sharing a little of latency for the garbage collector it includes.
Loris Cro simplifies the performance comparison in a nice catchy phrase:
Go is fast, but Rust is faster
In other words, Go can suffer from latency bound routines if you play your cards wrong. Rust as no chance to slowdown as there is no Garbage Collector.
It’s easier to compare Go with PHP than doing it directly with Rust since I come from PHP world, and you too, probably. Go feels something like in-between both worlds, but I will talk of some points of Rust nonetheless.
Note: I will refer to Go as Golang sometimes. Don’t worry, they’re the same. “Golang ” comes from the fact the official website is “www.golang.com”.
1. Go is static and (mostly) safe
PHP is a dynamically typed language, everything can be everything, and so forth. A few years ago PHP started to support typed arguments, returns and just recently typed properties, which the interpreter uses for some performance gains and code safeness. Rust, on the other hands, is explicit, and very little is left to chance.
Go is an statistically written language, meaning, you will have to put in your code the type of data you will use around your application, which adds to the safeness: you will mostly know what to send, and what to receive. Mostly.
Writing a function is easy: you set the parameters with their type, the returning value (or values) and you’re set.
func sumSomething(number int) int {
return number + 10
}func numberFruit(fruit Fruit) (int, fruit) {
return 10, fruit
}
Go is almost in the same park as Rust, as memory safeness is enforced, but Go can let the ball go out of court sometimes, since the compiler is not as strict as Rust. The worst case scenario is having a panic at runtime when something is not quite right.
That means also there is no type juggling like in PHP, but there are some kind of polymorphism. We’ll get to that, don’t worry. In the meantime consider that “Go is safe, but Rust is safer”.
func sum(number int) int {
return number + 10
}func main() {
// This won't fly on Go as it's considered a string
fmt.Println(sum("10"))
}
The return
word in Rust can be implicit at the end of the function, or used in early returns. Meanwhile, PHP and Go both use mandatory return
if you want to return something, which is also cool for early returns.
func sum(sum int) int {
return sum + 10
}
As a side note, you can use “naked returns” to make functions shorter. As the Go Tour states, “they can harm readability in longer functions”, so most of the time you will use normal returns unless your function is very short like this:
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
2. The documentation is also awesome
Remember that Rust comes with its own online playground? Well, Go has an online playground too so you can test your code to learn about what’s good and what’s wrong.

Go follows the same steps that Rust with a nice “tour” that allows you to grasp the basics syntax. Most of the basics like imports, functions, variables and so forth, are explained there, and can be run using the playground right beside it.

As with the Rust introduction, I totally encourage the Go Tour for first timers.
3. Installing Go is braindead easy
PHP gets ton of criticism for its installation procedure. Rust it’s easier than PHP, but on Windows you will need some additional software. Golang… just execute the installer and you’re set.

Yeah, the screenshots is from a Windows based browser, but you can still install and use Go in your MacOS and Linux machine.
That’s was everything you need for playing with Go. No, seriously, that’s it!
4. Variables are… variables!
While in Rust variables are immutable by default, you will be glad to know that doesn’t apply in Go. Much like PHP, variables are meant to be changed, but you still need to declare them with their type.
You will mostly use :=
to infer the type while declaring the value, but you can also do it separately. Leaving a variable (specially a struct) uninitialized is very rare.
func doSomething() {
var favorite_number int
favorite_number = 10 other_number := 33
}
As you can see, the above allows to have variables with default values or being nil
(more on that later). In PHP you can do the same using $empty = null;
, which is very rare.
One thing to note is that you cannot redeclare a variable. Once it’s set, it is until the function ends.
var number int = 10 // <-- Here it's declared.
var number string = "hello" // <-- This won't compilation error.{
var number int = 20 // <-- This works in this scope.
}
What if you want a constant inside a function? You can do it.
5. Strict constants and no static
Go is stricter than PHP for its constants, and far more than Rust. They can appear wherever they can, like inside functions, but the only values allowed to be constant and unchangeable in the lifetime of the application are single characters, strings, booleans and numeric values (like integers and floats).
func main() {
// This works:
const A_STRING string = "Hello world" // This doesn't even compile
const AN_ARRAY_OF_STRINGS = [2]string{"Hello", "world"}
}
This differ from what in Rust is known as “immutable variable”. In Rust, all variables are immutable by default so you must use len mut my_variable
more than often.
6. Dude, where is my static?
That’s nothing compared to the absence of static variables. Go doesn’t support the concept of a variable that can be “static”, meaning, can live as long the application does and can be changed by anything. Instead, you can create global variables and global constants that you can use, right beside some other functions, that can apply the same concept.
var global_variable = "Hello World"const GLOBAL_STRING = "This is constant"func main() {
// ... very important stuff fmt.Println(global_variable, " and ", GLOBAL_STRING)
}
Global values are mostly defined “at the top” of a package, can be accessible anywhere, and are treated like poison when used without control. If you plan to modify a variable shared across your application, you may want to use Channels or Mutual Exclusion (Mutex) to avoid corruption.
7. Everything is passed-by-value
While in Rust the concept of passing-by-value differs by “moving” instead of “copying” a value into a function call, you will find that Go is very much like PHP: everything is passed-by-value.
func sum(number int) int {
number += 10 return number
}func main() {
number := 10 fmt.Println(sum(number)) // This will output "20"
fmt.Println(number) // But this, will output "10"
}
In other words, the value will “copy” itself into the function scope where is being received, so in the called function you will not be able to modify the original value. In Go, the exceptions to this rule are slices, maps, functions, channels and the pointers themselves.
If you plan to pass a struct or any other value that’s is large, but you don’t need to modify the original value, it’s miles better to pass the pointer instead of letting Go copy the whole data. Imagine a large JSON string being copied over and over, just to check if it holds a value. Madness!
Go may look like it’s compatible with pass-by-reference, but is not, because no reference can be “null”. Instead, it uses pass-by-pointer, which it’s the same that happens in PHP behind the scenes: a copy of the pointer in memory is copied instead of the real value.
func sum(number *int) int {
*number += 10 // Get the value under the pointer and update it. return *number // Return the value under the pointer.
}func main() {
number := 10 fmt.Println(sum(&number)) // This will output "20".
fmt.Println(number) // This outputs "20" because we updated it.
fmt.Println(&number) // Prints the memory address like "0x...".
}
Pass-by-pointer means passing a copy of the pointer (an address of memory) to a function, instead of the real value. We can use *number
to get the actual value under that pointer.
Don’t fear using pointers: if you need to modify the original value, or the data being passed is too big, you should receive the pointer. You won’t be hit by 10% performance regression as it happens in PHP when passing-by-pointer because the interpreter is heavily tuned for copy-on-write.
One key difference between pass-by-reference and pass-by-pointer is that pointers in Go can be pointing to nil
, or “null” as you may know from PHP, if you say the pointer complies with an interface but was initialized without value. Hold on, we are gonna discuss that just after interfaces.
8. Garbage Collector done… right?
While Rust doesn’t have a Garbage Collector, instead opting for the simpler “ownership” technique of programming, PHP and Rust do have, but these differ in their implementation.
The Garbage Collector in Go, galled GOGC, marks memory in use by a goroutine and the variables that should be swept. Once the Heap memory doubles its size, it cleans those not in use in a concurrent manner to avoid pausing the application too often — also known as “Stop the world”.
There is some trickery to do it while other goroutines are executing, but the implementation is surprisingly very clean.

While most of the time you won’t need to think about it, Go conveniently exposes a knob to trade memory usage for CPU usage: too hasty and your application will worse its latency by calling it too much times; too lax and it will eat a lot of memory until it frees it completely.
GOGC it’s something that you will have to tune if you experience a very rare case of performance problem, specially on latency-bound applications where milliseconds can do a difference: multimedia processing, data streaming, human interaction an so forth.
If you want to deep dive on memory management and garbage collection, there are two articles, one in Ardan Labs and the second one in Deepu’s Blog.
9. Don’t think about the Stack or the Heap
In PHP, almost everything goes to the Heap. In Rust, you know when something is on the Stack or the Heap by just looking at the code.
Go, on the other hand, follows the same philosophy as PHP by not telling you where something lives in the Stack or Heap; Go compiler will decide what’s the best for you: to push a value in the Stack since it always has a known size, or allocate it on the Heap because it doesn’t.

That may have drawbacks on some cases, for example, if you want to manually use the stack, or make manual memory arrangements to avoid letting the compiler figure it out. For that, Rust is.
10. No threads, no async, just Go
In PHP, concurrency is non-existent. In Rust, there is a separation between threads and async functions. Go meshes together threads and async functions into what’s called as “goroutines”.
Goroutines are not function signatures, but how they are invoked into the scope. Just call a function prefixing go
, and that’s all, there is your new goroutine. No need to push callbacks to a thread, async-await or who knows what else. And if you want to wait for all goroutines to finish their work, just use a WaitGroup
.
There is some behind-the-scenes magic you don’t have access to. In a nutshell, Go implements it’s own “task scheduler” instead of relying on the OS scheduler alone. Every task, called goroutine, is handled by this scheduler that comes for free into your application.
Goroutines are an abstraction for handling concurrency through functions. It takes you away from the deeper manipulation you could have in Rust: think not about threads and async functions, but instead what can be done asynchronously. The Go Scheduler will deal with threads and async functions for you.

About threads, a CPU (logic) core can handle 1 thread or 1.000.000 of threads. What it takes performance from it is switching contexts between each of them.
I honestly recommend this article about how Go handles goroutines, if you want a more complete view about how and why. In any case, you still have some control over threads, but forget about the low level access that you could have with C++ or Rust.
11. Still not the arrays you’re looking for
PHP is very lenient on their concept of arrays, which on the low level are a Hash list with Enums for each value type that list holds.
$mylist = ["foo", new Bar, 3]; // This is valid.
Go shares part of the same concept with Rust: arrays are lists of fixed size and value types, while slices are sections of that arrays. You can add more elements to the array, which will create a new array. Go will double the size of it when you put more things on it to avoid re-allocating the whole list every damn time, which is what PHP does behind the scenes.
var list := [2]string{"Hello", "world!"}
So, if you want to tight your memory usage, it’s just better to have arrays with a known capacity beforehand, or have room to spare.
Now, for the case of key-value lists like in PHP, Go offers Maps as long its members share the same type.
var thisMap = map[string]string{
"greeting": "Hello",
"subject": "world!",
}
But what about an array of different types? You will need to declare the array with an empty interface type — hold on, we are going there — and then make type assertion where you need it. See, this is what I described as “let the ball go out of court from sometimes”.
func main() {
var list := []interface{}{1, 2, "apple", true}
fmt.Println(arr) // however, now you need to use type assertion access elements
i := arr[0].(int)
fmt.Printf("i: %d, i type: %T\n", i, i) s := arr[2].(string)
fmt.Printf("b: %s, i type: %T\n", s, s)
}
As arrays are a little more lenient than Rust in Golang, we enter the real deal: structs.
12. Not your OOP language either
PHP has a long standing with OOP, and it’s mostly the preferred way to handle code when writing on it. Go, as with Rust, separates data and behavior.
In Go you have structs, and implementations of behavior over that struct. Go likes to keep things tidied up, so you will mostly add behaviour over a struct by creating a function pointing to that struct, and just then adding the function signature parameters.
type Fruit struct {
family: string,
name: string,
}func (fruit Fruit) setFamily(family string) {
fruit.family = family
}
Seems like a very weird way to declare behavior over a data type, so in my perspective this can be more confusing to read at first glance.
13. Newtypes in, Tuples and Enums out
PHP doesn’t support tuples or enums anywhere, but if you’re new to Go, you will only have to worry about Newtypes. They’re basically an alias for a given type that come with zero cost and makes your code more idiomatic.
type UserName stringfunc whatIsYourName(un UserName) {
fmt.Println(un)
}
But the biggest absence is Enum. As with PHP, Go doesn’t have native Enums, which are “enumerable” types that can be one of multiple possibilities.
You will have to work around this by using a custom Type as integer, and assign to it constants expressions with an “iota”, which allows this constant “options” to be aliased internally as integers. If none of that made sense, look this code:
type Color int
const (
Red Color = iota // Make each constant an integer
Green
Blue
)func main() {
var color Color = Red if (color == Red) {
fmt.Println("The color is: ", color)
}
}
In PHP you may have to create a Class with public constants inside of it, and assigning them an integer, string, or whatever you feel that is more appropriate. Even I had to tackle this problem in PHP with my Laratraits package.
About tuples, you will mostly use structs instead, or functions returning more than one value. The most close thing to Tuples in Go is a function returning more than one value:
func returnsFooAndBar() (int, int) {
return 1, 2
}foo, bar := returnsFooAndBar()
In Rust there is no need to do this, Enums come for free out of the box and Tuples too if you want to use them.
14. There is no “static methods”
In PHP you can attach static methods to a class, meaning, you can call a given logic identified by a class name. You usually create them there to handle a logic that is pertinent to the class, like constructors.
class Equation {
protected $color = "transparent"; public static function new()
{
return new Equation;
}
}$equation = Equation::new();
In Rust, you can also have static methods attached to a struct. No problem there either.
struct CarChassis {
color: String
}impl CarChassis {
pub fn new() -> Self {
CarChassis {
color: "transparent"
}
}
}
In Go, there is no such concept of static methods. Since functions live under their own “package” or “namespace” (we will there shortly), you cannot create idiomatic constructors or methods. Instead, you will have to rely on normal functions, and if you want a constructor, append New
to the function as it has become the convention by the community.
type CarChassis struct {
color: string
}func NewCarChassis() *CarChassis {
return CarChassis {
color: "transparent"
}
}--- Later in the codevar car = NewCarChassis()
15. Functions have variadic parameters
PHP is very lenient about having functions with default or optional parameters. Rust forces you to abide to the function signature.
Go is like Rust, but less strict, though. You can have polymorphism with empty interfaces (hold on), and you have variadic parameters. This makes some functions calls convenient as these won’t let you deal with passing full arrays, which translates in less code.
func compare_cars(cars ...Car) bool {
// ...
}func main() {
// ... compare_cars(car_1, car_2, car_3)
}
But, apart from that, there is no default or optional parameters for a function.
16. Defer is magic
The “defer” is a magic keyword in Go that allows to push a function call to the end of the current scope.
func write_something(file File, text string) {
defer file.Unlock() // If the file cannot be locked, the program panics, but the
// deferred `file.Unlock()` will be called nonetheless.
file.Lock() // And if we cannot append the text, it will also be called.
file.Append(string)
}
There are some simple rules, but in a nutshell, it’s a very good way to ensure something gets executed no matter what the outcome, even panics, meaning you won’t duplicate cleaning procedures anymore, like deleting temporary files or closing connections if something goes wrong.
Rust has a non-official package called ScopeGuard that implements the same behavior, and in PHP you can make the same using the try/finally
:
public function readFile($file) {
try {
return $this->filesystem->read_contents($file);
} finally {
// This will be executed even if no exception is returned.
$this->cleanup($file);
}
}
A deferred function can be used to recover the goroutine from panicking, as we will see later.
17. My old friends “if”, “for” and “switch”
You will feel at home with Go if you come from PHP, as the if
, for
and switch
statements work in a simpler manner.
The if
works like you expect but the parenthesis are optional. It allows to execute a statement before the condition that only lives for the condition block scope. Curling braces are mandatory, and there is no ternary one liners.
if roses == "red" {
fmt.Println("Roses are red")
} else {
fmt.Println("Roses are not red")
}if _, ok := flower.(Rose); ok && roses == "red" {
fmt.Println("Yes, this is a rose and it's red")
}
The switch
too is familiar and simpler: it only executes the first true case, so there is no need to add break
at the end of each one. If you don’t put the value to compare, then it’s assumed you’re using true
.
switch v {
case "red":
fmt.Printf("This is a red rose")
case "blue":
fmt.Printf("This is a blue rose")
default:
fmt.Printf("This is not a red or blue rose!")
}
By the way, it also works for Types, something in PHP you would do with multiple if
blocks and the instanceof
comparison. Kind of amazes me how much time PHP has not though of a solution that in Go just comes out of the box:
switch v := i.(type) {
case Rose:
fmt.Printf("This is a Rose")
case Violet:
fmt.Printf("This is a Violet")
default:
fmt.Printf("This may be not a flower at all!")
}
Finally, for
loops are the only way to loop through iterable data. Dropping the semicolons makes it a while
loop, and using nothing loops the code forever.
for i =: 0; i < 10; i++ {
fmt.Println("The number is: ", i)
}for isSystemReady() {
fmt.Println("The system is not ready... yet")
}for {
fmt.Prinln("I can't get out!")
}
And if you want to iterate over an array, slice or map, you can use the range
keyword which is a safe bet to not go out of bounds.
colors := []string{"Red", "Green", "Blue"}for index, color := range colors {
fmt.Println("The color is: ", color)
}
But the for
loops are so simple that poses a problem for iteration in the next point.
18. There is no Iterable type
Go will loop fine with arrays, slices, channels, maps, and even strings. The problem is that you can’t make another type loop elegantly as an Iterator.
Go doesn’t implement an “Iterable” type, meaning, a standard way to get the next item from a list until the end. For example, if we want to make a “KeyChain” iterable, we will have to implement our own iterator logic, outside and inside the loop:
PHP and Rust have these Iterable types implementing the needed logic to traverse through loops, putting less strain on the developer. The problem when you don’t have this type from the language itself is simple: everyone will make their own flavor of iteration for the same purpose.
19. Nil is not a type, but a value
The concept of “nil” in Go is very simple: is just another value. The only types that can have a nil value are pointers, slices, maps, channels, functions… and interfaces. Hold on the last one, we’re very close to explaining them.
var number []int // number == nil -> true
var list map[string]string // list == nil -> true
var pointer *int // pointer == nil -> true
var channel chan int // channel == nil -> true
var function func() // function == nil -> truetype Something struct {
pointer *int
}var something Something // Something.pointer == nil -> true
var anyType interface{} // anyType == nil -> truevar anything = nil // compile error: use of untyped nil
For the rest, you will get default values:
- an uninitialized
string
will be empty, - an uninitialized number will be
0
, and - an uninitialized
bool
will befalse
.
Why would you use nil
? Well, most developers will use it to check if a function has returned an error or not with if err != nil
.
Now, that’s only the tip of the iceberg. One of the rare occasions or “edge cases” where nil can become a nuisance at runtime is when its used for interfaces.
20. Interfaces are like ducks
In PHP, you must explicitly declare which interfaces a class implements, and the needed signature methods for each. In Go, interfaces still exists but you don’t have to declare implementations, Go will figure it out for you.
type Duck interface {
Looks() string
Swims() string
Quacks() string
}type Animal struct {}func (a Animal) Looks() string {
return "duck"
}func (a Animal) Swims() string {
return "duck"
}func (a Animal) Quacks() string {
return "duck"
}
It’s basically the Duck Test:
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
This seems kind of weird. If you look at the code, indeed the methods of the interface are present, but there is no explicit declaration for the “Duck” interface. Go assumes you’re implementing that.
For example, if you implement the String
method into a struct, it magically becomes a Stringer
implementation, and the method will be used for printing what it returns.
type Thing struct {}func (t Thing) String() string {
return "Hello world!"
}func main() {
fmt.Println(new(Thing)) // <-- "Hello world!"
}
Interface methods are automatically declared as public, but as weird as Go can become, if you define swims()
starting with lowercase, it will still be a public method.
type Duck interface {
swims() string
}
If you plan to make an interface, use uppercase in the first letter or everyone will get it wrong, even you.
21. Empty Interfaces. You read that right.
This is getting out of hands. Go allows for empty interfaces, meaning, anything. If you want a function to receive anything, you can do it by telling it to receive an empty interface, which is the base interface that every type implements.
func receive(anything interface{}) {
// ... What type is "anything"???
}
There are a lot of “gotchas” about empty interfaces in Go that may put you off a bit, like the one above. There is a nice article by Uday Hiwarale about handling interfaces in different shapes to avoid having errors at runtime rather than at compilation.
Let’s say an empty interface is like when a function in PHP receives any value.
22. Interfaces can have nil values, and that’s evil
The problem in Go is comparing values to nil, because a comparison is strict, meaning, both types and values must match.

One of the pitfalls of nil is when you expect a value for an interface type. In the case below, value
is of type SomethingInterface
, which is the pointer to an address, but the value of that address is nil
.
So if you say value == nil
, you're asking the following:
“Is value
nil
of typeSomethingInterface
equal to valuenil
of typenil
?”
…and clearly isn’t.
type SomethingInterface interface {
DoAnything() string
}type Box struct{}func (box *Box) DoAnything() string {
return "I did something!"
}func isThisNil(value interface{}) {
if value == nil { // <-- This will be false!
fmt.Println("The box is nil")
} else {
fmt.Println("The box is NOT nil") // <-- And this will run!
}
}func main() {
// Here we got the pointer of Box, with no data.
var box *Box = nil
// This says the pointer implements an interface, and it's valid
var pointer_of_box SomethingInterface = box// Pass the pointer to the function.
// The function will get this as valid, and execute
isThisNil(pointer_of_box)
}
In PHP you can state in the function arguments that your variable value can be null
, and handle that possibility easily. PHP will throw an exception when a call shouldn’t pass null
to a function that requires data, for example.
So, again, most of the time you will be use nil to check if a function returned an error. It’s just better to avoid using uninitialized values all together and always rely on the your own default values, unless you are aiming for a very low memory footprint.
By the way, Rust doesn’t have pointers pointing to emptiness, but rather, it forces you to deal the absence of something, and won’t compile until you do so thoroughly. This instantly makes Rust a lot safer than Go, which may be good if you need 500% secure software and willing to sacrifice more time.
23. Public or Private, nothing in between
PHP has inheritance. Rust and Go doesn’t. Since there is no inheritance, there is no “Protected” visibility; methods in an struct can be declared as Public, meaning it can be called from the outside, and Private, where only the members of the object itself can see them.
While this looks simple enough, you have to think about that visibility happens only at package level.
type Fruit struct { // <-- This is public
family: string,
name: string,
}func (fruit Fruit) setFamily(family string) { // <-- This is private
fruit.family = family
}func (fruit Fruit) WhatFamilyIs() string { // <-- This is public
return fruit.family
}
Go likes to keep writing code at a minimum, so declaring the visibility is easy: if it starts with uppercase, it’s public, and if it starts with a lowercase, it’s private and you won’t be able to call it.
import ( "fmt", "fruits" )func main() {
fruit := Fruit { family: "Apple" } fmt.Println(fruit.WhatFamilyIs()) // <-- This will work
fruit.setFamily("Pineapple") // <-- But this won't.
}
There is an article in Digital Ocean that gives more details about visibility, but in the meantime, just keep that lowercase-uppercase rule in mind.
24. No traits whatsoever
I know that PHP sometimes can become very convenient. For example, using a trait over a lot of similar but different classes becomes very good to avoid rewriting the same thing over and over.
trait Barks()
{
public function bark() {
return property_exists($this, 'bark_sound') ?
$this->bark_sound : "woof!";
}
}
Rust offers extreme convenience by mixing traits and interfaces into just traits, heavily forcing inheritance over composition.
trait Barks {
fn bark(&self) -> String {
self.bark_sound
}
}
Go, on the other hand, doesn’t support PHP-style traits at all. If you want to reuse code, prepare to write more code.
25. Ok, error. Panic.
In PHP you throw exceptions when something happens and it’s not what you are expecting, so you mostly pollute your code with multiple try-catch in every part of your application unless you can catch-all on the the very top of your application.
Go goes the Rust route too: expect failure and allow a way to recover from it. Unwanted behavior is “encapsulated” in three ways.
The first and most “harmless” way to say if something was ok or not is by simply returning a boolean along the wanted value. It’s used mainly to note that you can still proceed but you’re compromising on something.
function AreYouOkay(number int) (int, bool) {
// Sum 3, and return false if the number was below 3
number += 3 return number, number > 3
}func main() {
number, ok := AreYouOkay(10) // If the number is not okay, print this next string line.
if !ok {
fmt.Println("Note the number is over 3")
}
}
The second is a very common normal convention: in your function you may also return an error along the returned value. This is considered breaking behavior, and the developer will have to act accordingly.
import (
"errors"
)func ThisMayFail(number int) (int, error) {
if int == 0 {
return 0, errors.New("You cannot use zero as number")
} return number + 3, nil
}
What above does is simple: it returns an integer and an “Error” type that accepts a text message with the description of the error. Since functions can return more than one value, you can use this to your advantage when calling something that can fail.
func main() {
number, error = ThisMayFail(0); if error != nil {
log.Fatal(error) // This properly terminates the program.
} fmt.Println(number)
}
Believe me, you will see a lot of code in Go doing
if error != nil
. Go tries to be simpler, but that it’s just being lazy.
While if error != nil
gives some way to recover gracefully, sometimes you won’t be able to. That’s why you can use defer
.
The third technique is using defer
to recover from a panic with recover()
, for more harmful situations where you need to handle nasty panics.
func dontFail() int {
// Ensure the call to `recover()` is executed regardless.
defer func() {
if recovered := recover(); recovered != nil {
return 0
}
}() ThisWillPanic(); // <-- This will panic return 10;
}
The above defers the execution of the recover()
function, which, in a nutshell, captures the panicking value, and also allows to return something to the caller.
26. Namespaces are packages
PHP namespaces are very lenient, but there is a standard called PSR-4 that forces you to mirror the directory of the file to use it as namespace. This makes autoloading of classes better than just using require_once
in all your proyect.
// app/MyApp/Something.php
<?phpnamespace App\MyAppclass Something
{
// ...
}
Rust forces the developer to use modules to reference other external code or crates. Go follows the same principle, but with a change of semantics: code inside a namespace is considered a package you need to import into the file scope. Modules are the external code you can download from the Internet or reference from other directories.
Packages in Go represent the directory name, much like namespaces work in PHP. Go files inside the directory reference the directory they’re in, so you can create a file in src/user/name.go
with the following contents:
// src/user/name.gopackage uservar PublicName = "John Doe"var privateName = "Maria Doe"
Then you will be able to reference them in your main.go
file.
package mainimport (
"fmt"
"user/name"
)func main() {
fmt.Println(name.PublicName)
}
Package names are encouraged to be very simple, like arrutils
, hashutils
and so forth, but personally I think it’s just better to use expressive names rather than short text that makes them illegible.
If you want to go deeper, there is an article with all nitty gritty details about packages, like initialization.
Now, imagine you want to use a package available in Github. This is how convenient is:
import "github.com/vendor/goodies"func main() {
goodie.Name()
}
We’re gonna talk about it in the next point.
27. Decentralized dependencies
When using packages in PHP you will mostly require them in your composer.json
, and download them from Packagist. Every serious PHP projects ends with thousands of files in the vendor
directory.
Rust follows the same through your crates.toml
, and downloads them from crates.io, but these crates are put into a global directory instead of the project directory.
Go is the same as it uses the go.mod
file to store the information about dependencies, and installs these libraries where Go is installed. But there is a big difference: it’s decentralized.
When you want to use dependencies, as you will most of the time, you initialize the go.mod
with go mod init
.
go mod init my-proyect# Or, if you want to publish it in Github
# go mod init github.com/myname/mypackage
Once done, you import a dependency with go get
and the location of it. You will mainly do this with Github repositories and other VCS, but these can also be directories.
go get github.com/my-vendor/my-package
You can append:
- a branch like,
my-package@master
, - a release like
my-package@2.0
, - or even an specific commit like
my-package@c5dw84
For example, we can use the GoDotEnv package to read an .env
file.
package main
import (
env "github.com/joho/godotenv"
)
func main() {
err := env.Load() // ...do something important
}
Yes, Go’s module system is convenient as hell, and decentralized, making it instantly dependant on the package itself rather than a registry. Also, by installing packages outside the project, you can forget about meddling with huge dependencies in each project directory.
There is more information about modules in the article in HoneyBadger if you want more deep explanation on how to use them to your advantage.
I cannot talk about how many modules are available for Go from third parties, as I should take into consideration Github, Gitlab, Bitbucket and what not. It’s difficult to say what’s the most used Go module, but you can check Awesome Go to know what are those that stand out.
As a side note, Go and gRPC get along very well with the official gRPC package for Go.
28. Reflection is like a tiny mirror
You may thought Go didn’t have any kind of “reflection” system like PHP does, instead opting for Macros or other code-generation utilities like does happen in Rust. No, it has one but its very simple.
The reflect
package includes utilities that are mostly used for getting types and values from variables, and call named methods.
var pi float64 = 3.14fmt.Println("The value is: ", reflect.ValueOf(x).String())
So no, you won’t be able to, for example, check the name of the function you’re in, like you can do dynamically in PHP.
There is a whole blog post in Golang about “The Laws of Reflection”, but consider two things from it: you will mostly use it when dealing with empty interfaces that you expect to have many different types, and there is a performance penalty for apps that rely heavily on latency.
29. Testing is much better, but not quite
PHP doesn’t have an official testing library, and PHPUnit has become the de-facto way to test code. Rust it’s in the same state, but comes with some assertion macros. In Go, you will find pleasing that an official testing package and some additional tools are included.
Just simply hit go test
and it will automatically execute any function that starts with Test{Name}(*testing.T)
, inside a _test.go
file that you need to put inside your package directory.
func returnTen() int {
return 10
}func TestReturnsTen(*testing.T) {
if returnTen() != 10 {
t.Errorf("Value returned is not 10.")
}
}
The testing system comes with setup and teardown procedures, and benchmarking, among other goodies, but not assertions, so you’re on your own to assert a value you expected or not.
About Mocking, Go also includes an official mocking package.
As you can see, testing in Go is very much ahead in terms of setting up tests. The only drawback is assertion, but most of the time it isn’t necessary. There are some faking libraries, so testing code should be less of a chore, meaning, less time being spent on checking the code works as expected.
30. Profiling is awesome… once set up correctly
One thing very handy to know what’s happening with the compiled binary is with a profiling tool. Go includes an official package for profiling normal execution and tests.
Profiling will allow you to check what happened in your app while it was profiled, like what was hugging the CPU to death, what goroutine took time to complete, and what decided to bite too much memory. It may take time to setup correctly, though.

The good part of this pprof
utility is that it can expose an HTTP endpoint so you can view the data without needing another program to parse it. Just don’t leave it out in the open.
31. Golang likes the web, and you?
I have to say that Golang feels more close to the web side of things than Rust, but still feels like a general purpose language than something strictly bound to the HTTP stack. It’s no surprise that Istio and Traefik use Go, Hugo also uses Go, and even Docker and Kubernetes were created using Go.
The top web frameworks for Golang are Gin, Echo, Chi, Fiber and Revel, but considering their approach, I find them more suited to microservices rather than full-fledged feature-complete swiss-army-knife frameworks for the web, as these lack some features that other framework like Laravel would offer. I wouldn’t mind to have a GUI with Vue, React or Angular on top of a full stack of microservices made on Go, and I think that should be the ideal approach.
One thing are frameworks, and the other thing is the pink elephant in the room: databases. Luckly, Go figured it out better than Rust.
32. Only one way to database in Go
As always, you have 2 alternatives when connecting to a database: you do it yourself, or you look for a package that already does that. In you pick the later, you will find that there are three competing ORMs in the Golang space: GORM, ent.go and SQLX.
What separates GORM and ent.go from SQLX is that these are almost full-featured systems to query the database, while SQLX seems like a nice wrapper around raw statements. It wouldn’t hurt if you are in total control, but you mostly won’t, and I consider Query Builders the best way to build a SQL statement without fearing SQL injections or manipulating strings like in 1995.
Compared to Rust, SQL in Go is very mature, mostly thanks to an official generic interface for SQL-like databases and a broad list of multiple database drivers, which makes easy to develop solutions to talk to the database engines.
In a nutshell, there is less to think about using the database in Go.
Bonus: It’s freaking hard to read
I hate to say it so, but Go suffers from a problem called “lazy typing”. The convention in Go is to short every word, including variables, functions, and whatever you do. Is there some typing tax I’m not getting?
func (c *Case) SetC(m int, n string) {
c.Sum(m).Named(n)
}
This only gets exacerbated when you figure out that types don’t have inherent methods, meaning, you have to call external functions to modify a primitive variable, much like happens in PHP.
func (n int) int {
return (int + 10) / 10 // In a perfect world, you would have done this:
// int.Sum(10).Divide(10)
}
You may say that the function overhead is unnecessary, but we have a compiler on the other side, so it would be the compiler’s job to transform the function calls to “better performant code” like (int + 10) / 10
.
The above is also true in PHP, primitive types have no methods. Rust, on the other hand, has types with inherited methods that not only allows your brain to understand better what’s happening, but also more tools to handle them.
For example, the usize
primitive in Rust has a lot of methods, like returning a power of a given number, for free, avoiding you the hell of operands stacks.
fn power_of_ten() -> usize {
let mut = 10; 10.pow(5)
}
In PHP, the only way to tackle this is to enclose the number into a class, with its own methods, while in Go is to do it in a struct with the same. Yes, you will have to do it yourself, if nobody hasn’t done it already.
I think it’s clear by now what Rust, Go and PHP offer to the developer. The differences between them do not form a small list, but if I could summarize everything in just a few words, I would do it like this:
- Rust offers you the toolset to make your own vehicle, from the engine to the chassis, but everything uses extra screws to secure your creation.
- Golang is like having an engine, which is a step less to create your own vehicle, some very basic tools, but some handy goodies to speed up the process.
- PHP is like getting a cab and telling the taxi driver where you want to go. You control the destination, but not how it reaches it.
If you ask me, Rust is the leader on safeness, Go on convenience, and PHP in speed, as you will easily create an application prototipe faster with it. Once performance starts calling at the door, I think it will be natural to check Go and Rust and pay the price for migrating.