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);
}
go
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
while
javascript
let i = 1;
while (i <= 5) {
console.log(i);
i++;
}
go
i := 1;
for i <= 5 {
fmt.Println(i)
i++
}
Infinite Loop
javascript
while (true) {
console.log('hello, go!');
}
go
for {
fmt.Println(i)
For Each
javascript
const array = [1,2,3,4,5];
for (const value of array) {
console.log(value);
}
go
array := [5]int{1,2,3,4,5}
for _, value := range(array) {
fmt.Println(value);
}
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();
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()
}
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)
}
pointer: Someone, noPointer: lico
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)
}
nums len: 5, cap:5
[1 2 3 4 5]
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)
}
nums len: 6, cap:10
[1 2 3 4 5 1]
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)
}
nums len: 7, cap:10
[1 2 3 4 5 1 2]
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)
}
0xc00000a360 0xc00000c280 0xc00000c280
[1 2 3 4 5]
[1 10 20 4 5 1]
[1 10 20 4 5 1 2]
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)
}
nums len:8 cap:8
[1 2 3 4 5 10 7 8]
numsA len:4 cap:4
[5 10 7 8]
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)
}
nums len:8 cap:8
[1 2 3 4 5 6 7 8]
numsA len:5 cap:8
[5 10 7 8 50]
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);
}
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);
}
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));
}
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
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));
}
devided by zero
0
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()
}
test
defer
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")
}
}
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")
}
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)