DEV Community

Cover image for Go 5 Interesting Concepts as a js developer
SeongKuk Han
SeongKuk Han

Posted on

Go 5 Interesting Concepts as a js developer

Go 5 Interesting concepts as a js developer

What is Go

  • Statically Typed
  • Compiled language
  • Easy to learn
  • Fast
  • Concurrency

Go is an open source programming language supported by google. It's easy to learn and has high performance and powerful tools. You can easily make concurrent programs with goroutines.

It's commonly used for server programming like Web Development and Cloud & Network Services. Actually, you can develop any programs you want with it.


5 Interesting concepts as a js developer

  • For loop
  • Structs
  • Arrays and Slices
  • Handling Errors
  • Goroutines

For loop

In most languages, there are loops while, do-while and for.

Go has only one looping construct. the for loop.
But it has various shapes.

For

javascript

for (let i = 1; i <= 5; i++) {
  console.log(i);
}
Enter fullscreen mode Exit fullscreen mode

go

for i := 1; i <= 5; i++ {
    fmt.Println(i)
}
Enter fullscreen mode Exit fullscreen mode

while

javascript

let i = 1;
while (i <= 5) {
  console.log(i);
  i++;
}
Enter fullscreen mode Exit fullscreen mode

go

i := 1;
for i <= 5 {
    fmt.Println(i)
    i++
}
Enter fullscreen mode Exit fullscreen mode

Infinite Loop

javascript

while (true) {
  console.log('hello, go!');
}
Enter fullscreen mode Exit fullscreen mode

go

for {
    fmt.Println(i)

Enter fullscreen mode Exit fullscreen mode

For Each

javascript

const array = [1,2,3,4,5];
for (const value of array) {
  console.log(value);
}
Enter fullscreen mode Exit fullscreen mode

go

array := [5]int{1,2,3,4,5}
for _, value := range(array) {
    fmt.Println(value);
}
Enter fullscreen mode Exit fullscreen mode

no do-while

Only one loop. I think it's more clear than having a few loops. And Supporting infinite loop is also interesting.


Structs

A struct is a collection of data fields with declared data types. It seems like an interface. you can also attach methods into the struct.

javascript

class Company {
  employees = [];

  hire(...newEmployees) {
    for (const employee of newEmployees) {
      this.employees.push(employee);
    }
  }

  print() {
    for (const employee of this.employees) {
      console.log(employee.name);
    }
  }
}

class Employee {
  constructor(name) {
    this.name = name;
  }
}

const newCompany = new Company();

newCompany.hire(
  new Employee("lico"),
  new Employee("lavine"),
  new Employee("dave")
);
newCompany.print();
Enter fullscreen mode Exit fullscreen mode
package main

import (
    "fmt"
)

type Employee struct {
    Name string
}

type Company struct {
    employees []*Employee
}

func (company *Company) Hire(newEmployees ...*Employee) {
    for _, employee := range(newEmployees) {
        company.employees = append(company.employees, employee)
    }
}

func (company *Company) Print() {
    for _, e := range(company.employees) {
        fmt.Println(e.Name)
    }
}

func main() {
    newCompany := Company{}

    newCompany.Hire(
        &Employee{Name: "lico"},
        &Employee{Name: "lavine"},
        &Employee{Name: "dave"},
    )
    newCompany.Print()
}
Enter fullscreen mode Exit fullscreen mode

I'm also working on typescript projects, so, declaring types is not strange for me. But the way to attach methods to the struct is a little confused at the first time. And we don't use the concept "pointer". I think it's important to get familiar with the pointer concept. the memory. It's below the title 'struct' though, I'll show some examples for understanding the asterisk keyword next by struct names. (func (company *Company) ...)

import (
    "fmt"
)

type Pointer struct {
    Name string
}

func (pointer *Pointer) Change() {
    pointer.Name = "Someone"
}

type NoPointer struct {
    Name string
}

func (noPointer NoPointer) Change() {
    noPointer.Name = "Someone"
}

func main() {
    pointer := Pointer{Name: "lico"}
    noPointer := NoPointer{Name: "lico"}

    pointer.Change()
    noPointer.Change()

    fmt.Printf("pointer: %s, noPointer: %s\n", pointer.Name, noPointer.Name)
}
Enter fullscreen mode Exit fullscreen mode
pointer: Someone, noPointer: lico
Enter fullscreen mode Exit fullscreen mode

When you define functions with the asterisk keyword, you can access itself. you must consider the cost of copying objects.


Arrays and Slices

There are arrays and slices in Go. Arrays have fixed sizes but slices have flexible sizes. I expect the arrays are safer than slices because it's expected.

func main() {
    nums := [5]int{1,2,3,4,5}
    fmt.Printf("nums len: %d, cap:%d\n", len(nums), cap(nums))
    fmt.Println(nums)
}
Enter fullscreen mode Exit fullscreen mode
nums len: 5, cap:5
[1 2 3 4 5]
Enter fullscreen mode Exit fullscreen mode

Arrays can't be resized but slices can.

func main() {
    nums := []int{1,2,3,4,5}
    nums = append(nums, 1)

    fmt.Printf("nums len: %d, cap:%d\n", len(nums), cap(nums))
    fmt.Println(nums)
}
Enter fullscreen mode Exit fullscreen mode
nums len: 6, cap:10
[1 2 3 4 5 1]
Enter fullscreen mode Exit fullscreen mode

You can use resize with append. you might have noticed that the capacity turned into 10.
If you append one more,

func main() {
    nums := []int{1,2,3,4,5}
    nums = append(nums, 1)
    nums = append(nums, 2)

    fmt.Printf("nums len: %d, cap:%d\n", len(nums), cap(nums))
    fmt.Println(nums)
}
Enter fullscreen mode Exit fullscreen mode
nums len: 7, cap:10
[1 2 3 4 5 1 2]
Enter fullscreen mode Exit fullscreen mode

You must see like this. There are length and capacity in Go.
length is the length of the arrays or slices and capacity is the space that elements can settle in.
When appending an element, if the capacity is not enough, Go allocates new space and copies all of them to it.

func main() {
    nums := []int{1,2,3,4,5}
    numsA := append(nums, 1)
    numsA[1] = 10
    numsB := append(numsA, 2)
    numsB[2] = 20

    fmt.Printf("%p %p %p\n", nums, numsA, numsB);
    fmt.Println(nums)
    fmt.Println(numsA)
    fmt.Println(numsB)
}
Enter fullscreen mode Exit fullscreen mode
0xc00000a360 0xc00000c280 0xc00000c280
[1 2 3 4 5]
[1 10 20 4 5 1]
[1 10 20 4 5 1 2]
Enter fullscreen mode Exit fullscreen mode

When numsB was created, there was enough space. So, in this time numbB wasn't reallocated. This is why numsA was affected with changing a value of numbsB.

You can make a slice from arrays.

func main() {
    nums := [8]int{1,2,3,4,5,6,7,8}
    numsA := nums[4:]

    numsA[1] = 10

    fmt.Printf("nums len:%d cap:%d\n",len(nums), cap(nums))
    fmt.Println(nums)
    fmt.Printf("numsA len:%d cap:%d\n",len(numsA), cap(numsA))
    fmt.Println(numsA)
}
Enter fullscreen mode Exit fullscreen mode
nums len:8 cap:8
[1 2 3 4 5 10 7 8]
numsA len:4 cap:4
[5 10 7 8]
Enter fullscreen mode Exit fullscreen mode

numsA shared the memory space with nums. It's an also important concept of slices.

func main() {
    nums := [8]int{1,2,3,4,5,6,7,8}
    numsA := nums[4:]

    numsA = append(numsA, 50)
    numsA[1] = 10

    fmt.Printf("nums len:%d cap:%d\n",len(nums), cap(nums))
    fmt.Println(nums)
    fmt.Printf("numsA len:%d cap:%d\n",len(numsA), cap(numsA))
    fmt.Println(numsA)
}
Enter fullscreen mode Exit fullscreen mode
nums len:8 cap:8
[1 2 3 4 5 6 7 8]
numsA len:5 cap:8
[5 10 7 8 50]
Enter fullscreen mode Exit fullscreen mode

Slices that are made from Arrays is also able to expand.

In javascript, there is not much to care about when using Arrays. It will get used to it but it took a bit of time to understand at the first time.


Handling Errors

I might have not handled errors well in js. Error handling in Go, It's kind of fun though, it's really difficult. I haven't understood well yet so, I will show some basic examples about it.

javascript

function devide(a, b) {
  if (b === 0) throw new Error("devided by zero");
  return a / b;
}

try {
  console.log(devide(100, 0));
} catch (e) {
  console.log(e);
}
Enter fullscreen mode Exit fullscreen mode

go

func devide(a int, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("devided by zero")
    }

    return a / b, nil
}

func main() {
    result, err := devide(100, 0)

    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(result);
}
Enter fullscreen mode Exit fullscreen mode

Writing more code in Go than in javascript.
Let's say, there is an error that you don't expect. How can we prepare for accidental errors?

func devide(a int, b int) int {
    return a / b
}

func main() {
    fmt.Println(devide(10, 0));
}
Enter fullscreen mode Exit fullscreen mode
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.devide(...)
        C:/Users/hskco/OneDrive/바탕 화면/dev/example-a/go/main.go:8
main.main()
        C:/Users/hskco/OneDrive/바탕 화면/dev/example-a/go/main.go:12 +0x12
exit status 2
Enter fullscreen mode Exit fullscreen mode

You would encounter the panic with the code. You can recover the panic using recover in a defer function.

func devide(a int, b int) int {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("devided by zero")
        }
    }()
    return a / b
}

func main() {
    fmt.Println(devide(10, 0));
}
Enter fullscreen mode Exit fullscreen mode
devided by zero
0
Enter fullscreen mode Exit fullscreen mode

When errors occur, defer functions would be called. Also, defer functions are called after the function execution.

func test() {
    defer func() {
        fmt.Println("defer")
    }()
    fmt.Println("test")
}

func main() {
    test()
}
Enter fullscreen mode Exit fullscreen mode
test
defer
Enter fullscreen mode Exit fullscreen mode

You can clean up resources in the function. multiple defer functions are available as well.

var (
    CUSTOM_ERROR = errors.New("CUSTOM_ERROR")
)

func test() error {
    return CUSTOM_ERROR;
}

func main() {
    err := test()

    if err == CUSTOM_ERROR {
        fmt.Println("Custom Error")
    }
}
Enter fullscreen mode Exit fullscreen mode

It is a part of custom errors. It has a lot of features for custom errors.


Goroutines

Although there are features like workers, javascript is a single-threaded language.

Go has goroutines. A goroutine is a lightweight thread managed by the Go runtime.

They can communicate with other goroutines with low latency using channels.

func print(wg *sync.WaitGroup, nCh <-chan int) {
    defer wg.Done()

    // Receive an element from the channel
    for n := range(nCh) {
        // Print numbers
        for i := 1; i <= n; i++ {
            fmt.Println(i)
        }
    }
}

func main() {
    var wg sync.WaitGroup
    nCh := make(chan int)
    list := [...]int{10, 20, 30, 40, 50, 60}

    wg.Add(2)

    for i := 1; i <= 2; i++ {
        go print(&wg, nCh)
    }

    for _, n := range(list) {
        // Send an element
        nCh <- n
    }

    // Close the channel and wait the goroutines
    close(nCh)
    wg.Wait()
    fmt.Println("Done")
}
Enter fullscreen mode Exit fullscreen mode

Writing go keyword in front of the function name for executing a function in background. This is a goroutine.

sync.WaitGroup is used for preventing the exit program. When the main function is finished, all goroutines exit.

Channel is used for communicating with goroutines.

close(nCh) notifies that the channel is closed, then goroutines will break from for n := range(nCh).


Conclusion

It's been a long time since I learned a new language. It says easy to learn go, but for me it was not that easy to learn it. there were many concepts that weren't in javascript. But it's worth it. Every language has its own philosophy. I think learning a new language would be helpful even if you don't often use the language. I'm working as a frontend engineer, so, someone might say that's not that useful though, I really enjoyed it.

I'm sure it's going to be useful even while I'm
working on a frontend project with js. And I can also make a server for my personal projects.


Which language are you interested in these days?

Top comments (0)