I have been coding with Ruby for many years, and I am learning Go these days.
Noticing the differences between the two languages is a great way to learn, and since they are so many, I am learning a lot.
The two main differences are:
- Go is compiled. Ruby is interpreted.
- Go is a statically typed language. Ruby is not.
These are two common ways to classify programming languages. Let me clarify these a bit:
Go is compiled
Compiled means that Go comes with a compiler which translates the source code into bytecode, which can be efficiently executed.
Ruby is interpreted, which means that the execution of the source code starts immediately, but it is slower.
A Go program is faster than a Ruby program, but the Ruby one does not take time to compile, it just starts.
The topic of compiled and interpreted languages is complex and full of nuances, I simplified it to get to the point.
See: https://golang.org/cmd/compile/
Go is a statically typed
In Go, you must declare a variable and set its type before using it. After that, the variable can only accept a value of the same type.
For example, if the variable username is a string, and you try to assign the number 35 to it, you will get a type error.
The error is raised when you try to compile the code. That is a limitation, but it is a good one, which can save you hours of debugging.
In Ruby, the following is allowed:
username = "Alex" # username is a string now
...
username = 35 # After this, username is an integer
It works, but it's not a good practice.
Apart from these two main differences, there are a few things that surprised me about Go and might surprise you if you are a Ruby or Python developer.
In Go:
- You cannot set nil to a simple type variable
- The type system is very restricted
- There are not classes
- There are no exceptions
- Strings are immutable
- Array assignment duplicates the values
- Arrays are static
- "for" is the only loop available
- Go cames with an Auto formatter
- Public methods start with Capital letter
1. You cannot set nil to a simple type variable
If you try to compile
var username string
username = nil
Go will tell you:
cannot use nil as type string in assignment.
Simply it is not allowed.
The default value (also called zero value) for a string is the empty string "".
Go designers are committed to keeping things simple, and having nil for strings would add ambiguous conditions - What is the meaning of a nil string? How is it different from an empty string?
Other types allow nil value.
A pointer type variable contains the memory address of another variable. But when it points to nothing, its value is nil, which makes sense.
2. The type system is very restricted
In Ruby it's possible to sum an Integer with a Float number.
In Go you can not. You get a type error even if you want to sum an Integer with a Byte type.
This is because Go tries to prevent you from making mistakes, like using certain actions for the wrong types.
It's possible to sum numbers of different types in Go, but you must be explicit and convert them to the same type:
var year int = 2021
var period byte = 3
var endYear = year + int(period) // no error here, since we convert period to int
3. There are not classes
At first, this sounded weird. I wondered if Go didn't have methods either, and I found out that Go has methods, just they are relevant to the Types and not Classes.
Once I saw some examples it all made sense.
Ruby has Classes, Methods inside the classes, and instance variables that can be accessed from the methods:
class Dictionary
def initialize
@words = {}
end
def add_words(it_word, en_word)
@words[it_word] = en_word
end
def to_english(it_word)
@words[it_word]
end
end
In Go you can create a new type and new methods for that type:
type Dictionary map[string]string
func (d Dictionary) translate(engWord string) string {
return d[engWord]
}
4. There are no exceptions
In Go, the error statuses which trigger exceptions in Ruby (and in many other languages), do not stop the normal execution flow.
To explain this let's try to open a file in Ruby:
file = File.open('dictionary.txt')
puts file.read
If the filename has a misspelling, let's say "dictionari.txt" when you try to run the program, you get:
openfile.rb:1:in 'initialize': No such file or directory @ rbsysopen - ictionary.txt (Errno::ENOENT)
That's because the missing file is a critical error, and Ruby decides to stop the code execution.
Go has a different approach:
file, err := os.Open("dictionari.txt") // filename is misspelled
if err != nil {
panic("Error opening the file!"
}
Even if the file doesn't exist, the instruction Open("dictionari.txt") does trigger an exception.
A missing file is not an exceptional situation in Go, it is just an error than can be returned along with the result of the method call.
The author of the code decides if the missing file is a critical state which requires to quit the execution.
In short:
File.open('dictionari.txt')
raises an exception which can be rescued.
file, err := os.Open("dictionari.txt")
returns an error - in err
- if it cannot open the file. The author of the code decides to raise a panic, to stop the execution, or not.
5. Strings are immutable
In Ruby, it is possible and simple to modify a string. To replace a char you can assign a new char in a certain position.
For example:
book = "The go language"
book[4] = 'G'
puts word # this prints "The Go language"
In Go a string is immutable, and you can not replace any chars in it. You must create a new string with the changes, even if you want to change just one char.
To show this let's declare a string:
var book = "The go language"
and try to change a char:
book[4] = 'G'
This would return the error:
> cannot assign to book[4]
to avoid the error you must create a new string and reassign it to the var book:
book = book[:4] + "G" + book[5:]
book[:4]
and book[5:]
are the slices containing the string "The " and "o language"
6. Arrays are static
In Ruby, you can easily add an element to an array, under the hood the array is resized so the new element can take place:
languages = ['Ruby', 'Go', 'C', 'Python']
languages.push('Java')
puts languages # output: ["Ruby", "Go", "C++", "Python", "Java"]
As you probably guessed we cannot do this using Array in Go. Let's try:
languages := [4]string{"ruby", "Go", "C", "Python"}
languages = append(languages, "Java") // Error: first argument to append must be slice; have [4]string
fmt.Println(languages)
But we can make it work using a Slice instead of an Array with a small code change:
languages := []string{"ruby", "Go", "C", "Python"}
languages = append(languages, "Java")
fmt.Println(languages)
Can you spot the difference with the previews snippet?
The only change is that in the last snippet we did not set the size of the Array
(4), and this tells Go to create a Slice
to access the elements of the array. Slices are a flexible and common way to work with arrays.
To understand how Slices work read https://yourbasic.org/golang/slices-explained/
7. Array assignment duplicates the values
When in Ruby you assign a var containing an array to another variable, Ruby copies the references of the array.
Here is an example:
languages = ['Ruby', 'Go', 'C', 'Python']
l = languages
languages[2] = "C++"
puts language # output: ["Ruby", "Go", "C++", "Python"]
puts l # output: ["Ruby", "Go", "C++", "Python"]
We changed "C" to "C++" in the var languages
, but now "C++" is present in the var "l" too. This is because both variables refer to the same array.
Let's try the same instructions in Go:
languages := [4]string{"ruby", "Go", "C", "Python"}
l := languages
languages[2] = "C++"
fmt.Println(languages)
fmt.Println(l)
and this is the output:
[ruby Go C++ Python]
[ruby Go C Python]
Now, they are different, "C++" is only present in the first array. This is because Go create another array in the assignment l := languages
.
This was surprising at first, but then I learnt about another data structure: the Slice. As opposite as the Array, when you assign a Slice to another variable, only the reference is copied.
8. for
is the only loop available
Ruby is a flexible language, you can write the same logic in many different ways. loop, while, for and each are some of the ways you can iterate through Arrays. Also, you can use the functional methods: map, filter, reduce.
In Go, the only way to loop is using for {}
.
It is one, but it is very flexible:
languages := [4]string{"ruby", "go", "c", "Python"}
for i:=0; i < len(languages); i++ {
languages[i] = strings.ToUpper(languages[i])
}
This looks like the for
in C language, but in Go you can omit the i:=0;
and the i++
parts, which make it behave like a while:
i:=0
for ; i < len(languages); {
languages[i] = strings.ToUpper(languages[i])
i++
}
(the semicolons are optional) and you can even omit the condition, and this will make it a infinite loop:
i:=0
for {
languages[i] = strings.ToUpper(languages[i])
if i < len(languages) { break }
i++
}
These examples are just to illustrate how for works, the best way to iterate through an array is to use for with the range keyword:
for i, l := range(languages) {
languages[i] = strings.ToUpper(l)
}
9. Go cames with an Auto formatter
Go provides a tool to auto-format your Go code: gofmt.
gofmt
formats your code in a consistent style, improving its readability. Most Go developers use it, and many editors can easily integrate with it.
gofmt
surprised me because Ruby developers didn't have a formatter for a long time.
Recently Penelopezone and few other brave developers began writing Rubyfmt - a formatter for Ruby.
I watched a talk by Penelope. She talked about how difficult it was to create a formatter for Ruby (it has a very elaborate syntax) and the differences with Rubocop.
Rubocop is a static code analyzer, flexible and with hundreds of checks, but it is not strictly a formatter. Rubocop and Rubyfmt can be used together.
https://github.com/penelopezone/rubyfmt
https://blog.golang.org/gofmt
10. Public methods start with Capital letter
In Ruby, class methods or constants are public by default. If you do not want them to be accessible from outside the class, you must declare them after the keyword private.
For example:
class Dictionary
def to_english(it_word)
..
end
private
def get_word_from_db
end
end
Any methods in Dictionary
can call the private method get_word_from_db
, but nobody can call it from outside since it is private.
Go does not have Classes but Types, and the concept of visibility is different. All the identifiers are visible only inside the current package.
You can set the visibility of a method by putting it in a different package, and naming it with an initial Upper case or lower case letter.
An initial upper case letter means that Go exports the method, and you can use it from a different package, otherwise, the method is accessible only from inside the current packages.
For example, the package fmt
exports many methods, among which the well known Println
, which starts with the upper case P
.
Conclusion
The Go syntax is simpler than the Ruby one. What you write in Ruby in a couple of lines, could require many in Go.
The Go compiler and its type system make you feel you have everything under control.
Go is fun, but contrary to what many people say, it might be not so fast to learn, especially if you came from an interpreted language like Ruby or Python.
I haven't used Go for a big project yet. My feeling is that with Ruby you might build more with the same effort, but an app in Go is faster to run and would make it more maintainable over time, this is after all the reason engineers in Google created it.