DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for From Javascript to Go II
Kevin AlemΓ‘n
Kevin AlemΓ‘n

Posted on

From Javascript to Go II

Hey! In my last blog post, you read some key differences between JS and Go, and how you can start to familiarize yourself with reading Go code.

In this article, let's see some key differences between types & functions. Specifically, we will see:

  • Arrays / Slices
  • Dictionaries
  • Sets
  • Functions

Arrays / Slices

How are they made in Javascript?

As you may know, an array in JS is dynamically sized. That means you don't need to set its length when creating it.

const myArr = ['this', 'is', 'good'];

console.log(myArr); // ['this', 'is', 'good']

The array type on JS has some very handful functions on its prototype:

myArr.push('b'); // Will add a new value at the end
myArr.shift(); // Will remove the first element of the array
myArr.pop(); // Will remove the first element of the array
myArr.concat(['c']); // This will append ['c'] at the end of myArr

And the list goes on! JS has a lot of functionality predefined on array prototype. And you can extend it also:

Array.prototype.sayHello = function() { return 'hello' }
myArr.sayHello(); // Prints 'hello'!

So, as you can see, the JS API for arrays is vast and very useful!

How are they made in Go?

Go has 2: Slices & Arrays. What's the difference? Well, the difference is in its length. Arrays are statically sized, and cannot be extended after defined.

For example:

a1 := [3]int{1,2,3} // Will create an array with [1,2,3]
var a2 [3]int // Will create an array with [0,0,0]

When you declare an array, its length becomes part of its type. So you cannot use an array of length 2 as an argument to a function expecting an array of length 3:

var arr1 [2]int
var arr2 [3]int

func aDummyFunction(arr [3]int) { /* Do something */ }

aDummyFunction(arr1) // cannot use arr1 (type [2]int) as type [3]int in argument

aDummyFunction(arr2) // Good!

The history changes with Slices. Slices are not tied to any length, so they can increase on-demand. Even when every slice is backed up by an array, the length is not a problem for them! So you can have:

var s1 []int // Will define an empty slice
var s2 []int{1,2,3} // Will define an slice with [1,2,3]
s3 := []int{1,2,3} // Will define an slice with [1,2,3]

Does this mean that you cannot define a slice's length? Of course, you can:

s4 := make([]int, 10) // Will return a zeroed slice with length 10
s5 := make([]int, 10, 20) // Will return a zeroed slice with length 10 & capacity of 20

Hmmm... length, capacity, what's the difference between them?

Well, remember that I mentioned early that every slice is backed up by an array? So, slices are, underneath the hood, fancy arrays! Let's dig this a little deeper:

  • A slice's length is the number of items the underlying array has. So when you declare s4 := make([]int, 10) you are telling Go that the underlying array should start with 10 elements.
  • A slice's capacity is the max amount of elements the underlying array can hold without having to reallocate. So here s5 := make([]int, 10, 20), when you append to s5 the element number 21, Go will copy the underlying array to a new array with the double of capacity (so it doesn't have to reallocate in a while).

You can find a more detailed explanation (and how they work internally) in this blog post. For now, let's see how do we work with slices:

  • Appending values to a slice
s6 := []int{}

s6 = append(s6, 1) // S6 will contain [1]
  • Appending a slice to another one
s7 := []int{1,2}
s8 := []int{3,4}

s7 = append(s7, s8...)
  • Slicing an array
arr := [10]int{1,2,3,4,5,6,7,8,9,0}

s9 := arr[0:3] // s9 will have [1,2,3]
s10 := arr[:3] // Same as above
s11 := arr[7:] // s11 will have [8,9,0]

An important detail to have in mind while slicing arrays is that slices are just references to the sliced array. So, if you change the value of the slice, the value of the array will change in consequence. This is true while you don't append values to the slice since that action will force Go to create a new array to hold the values of the slice.

You can find more useful operations with slices here

Dictionaries

How are they made in JS?

You can use Objects Maps or WeakMaps to create dictionaries. Let's focus on Objects since they are more used by JS devs. How do we use Objects for this?

const x = {
  key1: 'value1',
  key2: 'value2',
};

// Retrieving key/values
x['key1'] // 'value1'
x.key2 // 'value2

// Adding new elements
x['newValue'] = 'value3';
x.newValue = 'value3'; // Both are the same, however, this is the preffered approach when you already know the key.

How are they made in Go?

In Go, there's just one type for maps: Map. A Map is a reference type, so it should be maked before using it. Also, assigning a Map to a new variable WILL NOT copy the internal data.

// Create maps
// Using make
m1 := make(map[string]int)
// Using a struct literal
m2 := map[string]string {
  "key1": "value1",
  "key2": "value2",
}

// Accessing values

value := m2["key1"] // "value1"
value, ok := m2["key2"] // "value2". The second value defines if the key was found, so
value, ok := m2["nonValid"] // "", false, since the key is not present on m2

Sets

How are they made in JS?

Again, JS has 3 types for manipulating sets Object, Set, WeakSet.
Let's see the Set type.

const set = new Set([1,1,2,2,3,3,4,4,5,5])
console.log(set) // 1,2,3,4,5 since Set values should be unique.

set.size() // 5
set.add(9) // [1,2,3,4,5,9]
set.remove(9) // [1,2,3,4,5]
set.clear() // []

How are they made in Go?

By the time of writing of this blog post, Go hasn't native support for Sets. However, you can still use a Map to recreate a Set. You can find more of it at this blog post.

Functions

How are they made in JS?

JS treats functions are First Class citizens. That means you can assign a function to a variable, pass functions as parameters and return functions from inside functions, allowing the creation of closures. You can also use lambda functions on JS!

function x() {};
const x = function() {};
const x = () => {};

And, let's see closures:

function y() {
  const i = 0;
  return function() {
    return i++;
  }
}

As you can see from the above example, a closure is like a backpack, where the innermost function carries the values from the outer function, in this case, the i variable.

In JS, a function can only return a single value, so return a, b is not permitted.

How are they made in Go?

Go also treats functions as first-class citizens. And also allows the return statement to return multiple values.

func x() {}
x := func() {}

Let's see closures!

func y() func() int {
  i := 0
  return func() int {
    i += 1
    return i
  }
}

The same logic is applied to closures. Just remember, a func returning a func should define it as its return type, even with the params the inner function will take.

func y() func(int, string, rune) rune {
  return func(i int, b string, c rune) rune {
    return 'c'
  }
}

So far, we have learned how we:

  • Declare arrays/slices in Go
  • Create maps
  • Build set like structures
  • Create functions & create closures.

In the next blog post, we will discover a little bit more about Go execution context & scoping.

So, let's Go!

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.