Go for a Rubyist
Coming from the world of Ruby and iOS, learning Go was an interesting experience. I thought it might be worth to share some things learnt on the way, and put up a simple transition guide for everyone thinking about giving Go a shot.
This post is not the most idiomatic Ruby or Go you’ll read, but it’s instead written in a way to point similarities between the two.
Some of the reasons for Go
What’s the reason people want to develop applications in Go? Here are some of mine:
- Deployment
- Performance
- Tooling
- It’s not that complicated
Ruby is a wonderful language. I love writing it and there’s thousands of people that do as well. What I don’t fancy with Ruby is deploying. Deploying your own web apps is something you can still deal with, but shipping a tool to many people is a hassle. They need to have the correct Ruby installed, all the gems fetched, and often you need to support Rubies down to 1.8.7.
I highly encourage you to check out ngrok. After you download it and get your mind blown, just think of this: you’ve downloaded a tunneling proxy server, that ships with a HTML frontend and Bootstrap1 in one binary file. No dependencies. You’ve just run it without installing anything.
Go has a built-in dependency resolver, testing and code coverage, unified code formatter2, cross-compiler, and many other tools. You can compile Go from your Mac for all the platforms, including Windows. Most of Go’s tools are standalone CLI apps, that can give any editor some serious IDE capabilities.
Finally, Go is a way smaller language than Ruby or ObjectiveC, so there’s less things to learn. Once you understand the basics, you’ll be able to read mostly all the Go code you find online. Let’s take a look.
There’s no objects
But don’t worry; You can achieve lots with structs.
car = Car.new
car.doors = 4
car.make = "Ford"
car.model = "Focus"
car.honk
return car
car := new(Car)
car.Doors = 4
car.Make = "Ford"
car.Model = "Focus"
car.Honk()
return car
// Note: this is not idiomatic Go, neither Ruby.
// It's intentionally written this way to point out the similarities.
Functions
You can make Go look more Object-Oriented by adding functions to structs. The following usage example demonstrates so:
def run_car(car)
car.engine.start
end
func runCar(car *Car) {
car.Engine.Start()
}
Go methods could be a bit hard to read at first. But it’s really easy to do so, if you consider that all the Go functions are written the same way:
-
func
keyword - receiver (optional)
- function name
- input (optional)
- output (optional)
The simplest Go function doesn’t declare input, output or a receiver.
func sayHello() {
fmt.Println("Hello!")
}
If you want to achieve the Ruby example from above, you need to add the Start() function on the Engine (receiver)
// Receiver Name
func (engine *Engine) Start() {
fmt.Println("WROOOM")
}
Now by nature, this function doesn’t return anything. Let’s say you want it to return an error if car doesn’t start:
// Receiver Name Output
func (engine *Engine) Start() error {
// ... ommitted
}
Finally, let’s say you want to delay starting by 2s
// Receiver Name Input Output
func (engine *Engine) Start(delay int) error {
// ... ommitted
}
How do I declare a class?
As we’ve mentioned before, there’s no classes and objects in Go, but there
are structs. Let’s try to declare that Car
example from above, both in Ruby
and Go.
In Ruby, we’ll make Car and Engine classes, and define methods inside them.
class Engine
def start
puts "WROOM"
end
end
class Car
attr_accessor :engine
def initialize
@engine = Engine.new
end
end
car = Car.new
car.engine.start
# => WROOM
In Go, we’ll make CarEngine and Car structs, and define functions on them.
type CarEngine struct{}
func (engine *CarEngine) Start() {
fmt.Println("WROOM")
}
type Car struct {
Engine: *CarEngine
}
func NewCar() *Car {
car := &Car {
Engine: &CarEngine{}
}
return car
}
car := NewCar()
car.Engine.Start() // => WROOM
Private and Public
There’s no such a private
keyword, or public static void main()
in Go.
In fact, doing so is pretty simple:
- private stuff starts with lowercase
- public starts with uppercase
That applies on the following examples, respectively:
// public
func HandleError(e error) {
}
// private
func handleError(e error) {
}
type Car struct {
Make string
Model string
nickname string // this is private
}
Where’s my do…end block?
Go does have first class functions, and you can use them as such: Ruby:
go_to_the_store do |products|
puts "I HAS BUY #{products}"
end
Go:
goToTheStore(func(products *Products){
fmt.PrintLn("I HAS BUY %v", products)
})
Casting
Some people get afraid of types. And I have to admit - it is a bit confusing after getting so comfortable with Ruby. Casting in Go looks (like everything else) like upside-down casting in e.g. Java or C#.
So if you want to convert a string
to byte array
(byte slice in Go, to be
correct), you would type
porsche := "Porsche"
bytes := []byte(porsche)
And, Vice-Versa
bytes := []byte("Porsche")
porsche := string(bytes)
Notice how parenthesies positioning is different from Java or C#,
(string)bytes
vs string(bytes)
. The array []
notation goes on the opposite side as well.
Instead of byte[]
, you type []byte
.
Dealing with JSON
This was one of the biggest challenges when I’ve started writing a web service. The concept is mostly this:
- You either receive some JSON from the request, and you want to make a real object out of it.
- Or, you already have an object and want to render it as JSON
- Or, you’re dealing with a
map
(Hashmap) that’s not a first-class object
Let’s assume the JSON we’ll try to work with looks like this:
{
"first_name": "Marin",
"last_name": "Usalj",
"address": {
"street_name": "Fake st",
"street_number": 456,
"city": "San Francisco"
}
}
If you want to deserialize this into first-class models, you need 2 structs:
type Address struct {
Street string
City string
StreetNumber int
}
type User struct {
FirstName string
LastName string
Address *Address
}
If you try to Unmarshal
this directly, the User
won’t deserialize properly.
To demonstrate the problem, we can go the other way - make a model
programatically and Marshal
it to JSON.
GO:
user := &User{
FirstName: "Marin",
LastName: "Usalj",
Address: &Address{
Street: "Fake st",
City: "San Francisco",
StreetNumber: 456,
},
}
jsonBytes, _ := json.Marshal(user)
fmt.Println("User: ", string(jsonBytes))
JSON:
{
"FirstName": "Marin",
"LastName": "Usalj",
"Address": {
"Street": "Fake st",
"StreetNumber": 456,
"City": "San Francisco"
}
}
If you take a look at the generated JSON, it’s not the same as the first one;
the fields have capitalized keys, so we need to map them. Don’t get scared of
backticks. They’re something similar to Ruby’s 'string', %Q(string)
and many
other ways you can generate a string. In this case, they allow you to use
quotes for JSON keys.
type Address struct {
Street string `json:"street_name"`
City string `json:"city"`
StreetNumber int `json:"street_number"`
}
type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Address *Address `json:"address"`
}
Awesome, we’re ready to go. Now serializing and deserializing JSON to structs works as expected. In the real life, you’ll mostly be getting this JSON from a HTTP request. Here’s a sample factory method that takes a request pointer and returns a new User.
func NewUserFromRequest(r *http.Request) *User {
var user User
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&user)
if err != nil {
fmt.Println("CHAOS")
}
return &user
}
The last part is dealing with your own maps. This might be useful when you’re doing some request processing and manual serialization. A real life example would be listening to web-hooks from many web services, and mapping them to an uniform interface.
We’ll extract request parsing in another method that’ll return a
map[string]interface{}
. This is the syntax for the most generic Hashmap
in Go. Basically, it says keys are strings, and values are anything
(strings, numbers, arrays, objects).
Then we’ll manually construct the user, as if the mapping didn’t exist.
func NewUserFromRequest(r *http.Request) *User {
userMap := MapFromRequest(r)
addressMap := userMap["address"].(map[string]interface{})
user := new(User)
user.FirstName = userMap["first_name"].(string) // this is how you cast
user.LastName = userMap["last_name"].(string) // this is how you cast
address := new(Address)
address.StreetNumber = int(addressMap["street_number"].(float64))
address.Street = addressMap["street_name"].(string)
address.City = addressMap["city"].(string)
user.Address = address
return user
}
func MapFromRequest(r *http.Request) map[string]interface{} {
var userMap map[string]interface{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&userMap)
if err != nil {
fmt.Println("CHAOS")
}
return &userMap
}
To wrap up, a more idiomatic way of constructing the user would be passing
everyting into initializer instead of using the new(User)
syntax.
func NewUserFromRequest(r *http.Request) *User {
userMap := MapFromRequest(r)
addressMap := userMap["address"].(map[string]interface{})
return &User{
FirstName: userMap["first_name"].(string), // this is how you cast
LastName: userMap["last_name"].(string), // this is how you cast
Address: &Address{
StreetNumber: int(addressMap["street_number"].(float64)),
Street: addressMap["street_name"].(string),
City: addressMap["city"].(string),
},
}
}
We need to Go deeper…
There’s no real meta programming like in Ruby, but there are some tricks
you can make using the reflect
package and the fact Interfaces implementation
is implicit.
To get some ideas, you may want to check out these packages:
Martini - a Sinatra inspred tiny web framework
Revel - a bit bigger web framework like Rails or Play
Convey - BDD testing framework with live reload and web frontend
Ginkgo - yet another RSpec-style BDD framework
Vim
If you’ve really got this far - here’s a bonus: I’ve mentioned that fancy tooling on the top of the post. There’s some neat tools that’ll make coding Go feel as you’re writing a scripting language like Ruby.
The simplest solution ever is to add the following mapping in Vim - it lets
you hit <leader> g
and watch the current file execute.
map ,g :w\|:!go run %<cr>
If you install this plugin, it’ll format your code properly before executing. It’ll also check your syntax and fix imports. If you’re using an auto-completion engine, it’ll provide a full-fledged, context-based autocompletion!
And, if you’re writing test-driven app, Convey will re-compile and re-run test each time you hit save.
If you’re writing a webapp, gin from codegangsta will reload the server each time you save.
Pretty cool, huh?
-
A CSS framework https://getbootstrap.com/ ↩︎
-
go fmt
orgoimports
↩︎