cover image for article

Golang: Structs

#go

Introduction

Moving on to the 9th part of the series, we will be understanding structs in golang. Structs are an important aspect of programming in Golang, they provide a way to define custom types and add functionality to them. We will be understanding the basics of operating on structs like declaration, initialization and adding functional logic into those structs.

Structs in Golang

Structs or Structures in Golang are the sequences or collections of built-in data types as a single type interface. Just like we have int, string, float, and complex, we can define our own data types in golang. They can consist of built-in data types as mentioned and also certain functions or methods which can be used to operate on them. Using structs we can create custom data types that can meet the specific requirements of our problem. We can define structs and later inside functions we can create instances of those structures.

Structures are like a template or blueprint representation of data. It doesn't hold the actual data in memory, it is just used to construct an object of that type. After defining a struct, we can create instances or objects of those structs. These instances actually hold data in memory in the run time, so we basically deal with objects in the actual program. We'll see certain concepts of creating instances, declaring and defining structs, accessing data from instances and so on in the following section of the article.

Struct / Class 

Template / Structure for creating custom data types 

- Properties  (variables and constants defined inside a structure)
- Methods     (functions that are bound to a struct)

Declaring Struct

We can declare structs by using the keyword type followed by the name of the struct, after tha name, the struct keyword itself, and finally sets of parenthesis {} . Inside the parenthesis, we define the structure i.e. which type of data is to be stored and the name of those respective variables.

type Article struct {
    title string
    is_published bool
    words int
}

We have declared a struct or a custom data-type or a class(not really) in golang with the name Article that has few associated properties/variables inside of it. We have title as a string, is_published as a boolean, and words as an integer value. This constructs a simple type of golang which has a defined structure. We can further use this Article struct as a data type in the main function or any appropriate scope for actually assigning the structure memory at runtime.

Struct Naming Convention

There are a few things that we need to understand and make a note of, especially the naming convention.

This might not be important right now but it is worth knowing for later use cases. Let's say we want to use a struct from other files or modules, for that the name of the struct in the file/script where the struct is defined should have the Capitalized convention. If you have a simple and single file script/program, you can keep it lowercased or camelCased .

Leaving that aside, for now, we will try to focus on the essence of the structs in golang.

Creating Instances/Objects of Structs

Now, after defining the struct we need to create instances or objects of them. This can be done in several ways like using Struct literal, Manual assignment, and using the new function. We'll look into each of them in this section.

Using struct literal

The most simplest and straightforward way to initialize a struct is to use the struct literal just like we did with Maps, Slices, and Arrays. We basically parse the values of the respective fields in the struct.

package main

import "fmt"

type article struct {
    title        string
    is_published bool
    words        int
}

func main() {
    golang := article{"Golang Intro", true, 2000}
    fmt.Println(golang)
}
$ go run struct.go
{Golang Intro true 2000}

We have created the object or instance of the struct Article using the shorthand notation or the walrus := operator. Inside the {} braces, we can assign values but those values need to be in the same order as defined in the struct definition, else it gives a compilation error of type mismatch . So, here we have assigned the value title , is_published , and word as Golang Intro , true , and 2000 respective in that order.

Using Key-value pairs

We can also use the key-value notation for assigning values in the instance. With the previous method, we need to specify and thus initialize all the properties at once, but using this method we have a bit more flexibility.

vim := Article{title: "Vim: Keymapping", is_published: false}
fmt.Println(vim)
$ go run struct.go
{Vim: Keymapping false 0}

Here, we have provided the key i.e. the variable name inside the struct, and then provided the value to it separated by a colon : . Using this way of initializing instances of struct we have better control and flexibility in providing a default value for that object. In the example above, we didn't initialize the property words but it already initialized to 0 since the object is created hence the memory allocation is completed, and thereby it needs to have a default value.

Using the new function

We can use the new function to create a new instance of a struct. Though we can't provide an initial value, using the new function all the properties are initialized with their respective default values. Further, if we want to modify the values, we can access each property (variables in struct) using the dot operator and assign the desired values.

django := *new(Article)
fmt.Println(django)
$ go run struct.go
{ false 0}

We have used the new function to allocate memory for an instance of struct with the provided name. This function basically allocates all the properties of a default value and returns a pointer to that memory address. If we store the result of the new function in a variable object, we would get a pointer but we need the object itself, so we use * before the new function so as to de-reference the memory address from the pointer.

So, we have stored the default values in the newly created object of Article structure in django , this gives the default values like an empty string "" , default boolean value false and default integer value 0 . If we don't dereference the pointer and use it like djagno := new(Article) , thereby we get a pointer in that variable as &{ false 0} . Hence we use * before the new keyword.

Accessing/Assigning values to properties

We can now change the values of the properties in the object of the struct using the dot operator. We basically use the instance object name followed by a . and the property name to set its value.

django := *new(Article)
fmt.Println(django)

django.title = "Django View and URLs"
django.words = 3500
django.is_published = true
fmt.Println(django)
$ go run struct.go
{ false 0}
{Django View and URLs true 3500}

So, here we have used the object name which is django , and access any property by name with the dot operator , thereby we set the value as per the requirement. Note, we have not used the := operator as the properties have already been initialized, we simply need to modify the default value.

Creating Functions associated to Structs

We can now move into creating functions in the struct, by adding functions/methods in structs we can incorporate a lot of functionality into the structure of our data type. For instance, we can set the value of a string as "Empty" or "NA" beforehand rather than empty string "" .

package main

import "fmt"

type Mail struct {
    sender     string
    subject    string
    sent       bool
    word_count int
}

func (m Mail) check_spam() {
    if m.subject == "" {
        fmt.Println("Spam!")
    } else {
        fmt.Println("Safe!")
    }
}

func main() {
    mail_one := *new(Mail)
    fmt.Printf("Mail one: ")
    mail_one.check_spam()

    mail_two := Mail{"xyz@xyz.com", "Golang Structs", true, 100}
    fmt.Printf("Mail two: ")
    mail_two.check_spam()
}
$ go run methods.go
Mail one: Spam!
Mail two: Safe!

We define a function associated with a struct by providing the struct-name and a parameter name which can be just used inside of the function. Here, we have used (m Mail) so as to reference the object of the struct provided to it. This basically binds the function to the struct and hence it becomes a method of that struct.

Further, we can access the properties from the struct by their name using the dot separator. We are just checking whether the subject property in the instance is empty or not and simply printing text to the console. We are accessing the function and calling it with the syntax as instance_name.function_name() , here the function name is check_spam and the object name is mail_one for the first instance. Thereby we have called the function which is bounded to the instance of the struct. As we have accessed the function after the instance name the binding of the function i.e. the statements (m Mail) has taken the current instance and parsed it as the instance of the struct. Hence we are able to access the current instance's properties within the function/method.

Adding a return statement

By simply providing the return type and return statement with value, we can create functions of specific return types.

package main

import "fmt"

type Mail struct {
    sender     string
    subject    string
    sent       bool
    word_count int
}

func (m Mail) check_spam() bool {
    if m.subject == "" {
        return true
    } else {
        return false
    }
}

func (m Mail) print_spam(spam bool) {
    if spam {
        fmt.Println("Spam!!")
    } else {
        fmt.Println("Safe!!")
    }
}

func main() {
    mail_one := *new(Mail)
    fmt.Printf("Mail one: ")
    is_mail_1_spam := mail_one.check_spam()
    mail_one.print_spam(is_mail_1_spam)

    mail_two := Mail{"xyz@xyz.com", "Golang Structs", true, 100}
    fmt.Printf("Mail two: ")
    is_mail_2_spam := mail_two.check_spam()
    mail_two.print_spam(is_mail_2_spam)
}
$ go run methods.go
Mail one: Spam!!
Mail two: Safe!!

We have modified the check_spam function which returns a boolean value. If the subject is empty it returns true else it returns false. Also, we have added a function print_spam function which takes in a parameter as a boolean value and prints text according to the value. This is how we work with functions in structs. We have parsed the return value of the check_spam function as a parameter to the print_spam function.

Constructor in Structs

Constructors are special methods that are invoked when the instance of a struct is created i.e. the properties are assigned an initial value or default value. In this way, we can perform basic operations which we need to perform after the instantiation of the struct.

Golang does not have built-in constructors, but it is quite easy to create one. We simply need to create a function with an appropriate name(don't clash it with the struct name!!), by providing all the parameters that are in the struct so as to initialize them, and finally the return value as a reference to the struct instance.

package main

import "fmt"

type Repository struct {
    name       string
    file_count int
}

func New_Repository(name string, file_count int) *Repository {
    file_count++
    name = "Test"
    return &Repository{name, file_count}
}

func main() {
    blog := *New_Repository("", 0)
    fmt.Println(blog)
}
$ go run constructor.go
{Test 1}

We have created a function that is technically acting like a constructor as it sets a default value to the properties in the structure. We have struct Repository containing name as a string and file_count as an integer. We created a Constructor function named New_Repository that basically takes in the properties in the struct, remember they haven't been initialized yet as we are writing the constructor for the very purpose. We have to parse the parameters with the initial value and let it modify once we have created the instance.

That's it from this part. Reference for all the code examples and commands can be found in the 100 days of Golang GitHub repository.

Conclusion

So, from this part of the series, we are able to understand the basics of structs in golang. We covered declaration, definition, and adding methods in a struct. This gives a glimpse of Object-Oriented Programming in Golang. Thank you for reading. If you have any questions or feedback, please let me know in the comments or on social handles. Happy Coding :)