One of the questions I had while learning Go was:
If Go doesn't have classes, where do I put my data?
In Ruby, I'm used to creating a class and defining its attributes and behavior there:
class User
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
user = User.new("Alice", 30)
Go doesn't have classes.
Instead, one of the building blocks it gives us is the struct.
A struct is a collection of fields that allows us to group related data together.
Structs in Go
A struct is defined using the struct keyword:
type Person struct {
Name string
Age int
}
Here we're defining a type called Person with two fields:
-
Name, which is astring -
Age, which is anint
We can then create a value of that type.
person := Person{
Name: "Alice",
Age: 30,
}
And access its fields using .:
fmt.Println(person.Name)
fmt.Println(person.Age)
This feels quite similar to accessing attributes on a Ruby object:
person.name
person.age
But the underlying idea is different.
Creating Struct Values
There are several ways to initialize a struct.
The first is to provide the values in the same order as the fields:
person := Person{"Alice", 30}
We can also explicitly specify the field names:
person := Person{
Name: "Alice",
Age: 30,
}
This second form is easier to understand, especially when a struct has many fields.
We can also initialize only some fields:
person := Person{
Name: "Alice",
}
The fields we don't specify receive their zero value.
So Age would be:
0
We can even create an empty struct:
person := Person{}
In that case, all fields get their respective zero values.
This connects nicely with what we saw in Part 2 when talking about zero values.
Structs and Pointers
Structs can also be used with pointers.
For example:
person := &Person{
Name: "Alice",
Age: 30,
}
Now person is a pointer to a Person.
We can still access the fields directly:
fmt.Println(person.Name)
Go allows this as a convenient shorthand for dereferencing the pointer first.
Conceptually, this:
person.Name
is an easier way of working with:
(*person).Name
This was another place where Go reminded me of C++.
As a Ruby developer, pointers aren't something I normally think about when working with objects.
In Go, understanding whether you're working with a value or a pointer becomes much more important.
Creating Your Own Types
Go also allows us to create a new named type based on another type.
The syntax is:
type <typename> <datatype>
For example:
type Banana int
Now Banana is its own type based on int.
We can declare a variable using it:
var myBanana Banana = 50
This might look unnecessary at first.
Why create Banana if it's basically an int?
The important idea is that we're giving a type a specific meaning.
Instead of having an application full of anonymous int values, we can create named types that communicate what those values represent.
Ruby's Approach
Ruby is dynamically typed, so we normally don't create separate primitive-based types in the same way.
For example:
age = 30
The variable simply references an Integer.
If we want to represent something like a person, we'd typically create a class:
class Person
attr_accessor :name, :age
end
Go separates these concepts.
A struct gives us a way to group fields:
type Person struct {
Name string
Age int
}
And a custom type gives us a way to define a new named type:
type Banana int
There doesn't need to be a class involved.
Struct vs Ruby Class
This is where I initially had to change my mental model.
A Ruby class usually combines data and behavior.
class Person
def initialize(name)
@name = name
end
def say_hello
puts "Hello #{@name}"
end
end
A Go struct, on the other hand, primarily describes the data:
type Person struct {
Name string
}
And later, behavior can be associated with the type using methods.
For example:
func (p Person) SayHello() {
fmt.Println("Hello", p.Name)
}
Then:
person := Person{Name: "Alice"}
person.SayHello()
We'll look at methods more closely when we continue exploring how Go handles behavior without classes.
Go vs Ruby
| Concept | Go | Ruby |
|---|---|---|
| Group related data | struct |
class, Struct, etc. |
| Define a struct | type Person struct { ... } |
class Person |
| Access fields | person.Name |
person.name |
| Initialize | Person{Name: "Alice"} |
Person.new("Alice") |
| Missing fields | Get zero value | Depends on initialization |
| Empty value | Person{} |
Person.new |
| Pointer to value | &Person{...} |
No explicit pointer syntax |
| Custom type | type Banana int |
No direct equivalent in the same type-system sense |
Main idea
Go doesn't really ask you to think in terms of classes first.
Instead, you can start with the data:
type Person struct {
Name string
Age int
}
And that's it.
No constructor is required just to create a value.
No inheritance is involved.
No class hierarchy is necessary.
You simply define the type and create values from it.
But TBH I still like Ruby's object model.
When working on a Rails application, classes provide a natural place for models, services, and domain behavior.
Top comments (0)