Gopher Academy
3.87K subscribers
927 photos
40 videos
280 files
2.09K links
πŸ•Έ Gopher Academy

πŸ”·interview golang
https://github.com/mrbardia72/Go-Interview-Questions-And-Answers

Ψ­Ω…Ψ§ΫŒΨͺ Ω…Ψ§Ω„ΫŒ:
https://www.coffeete.ir/mrbardia72

Ψ§Ψ―Ω…ΫŒΩ†:
@mrbardia72
Download Telegram
Slice pre-allocation

#tips #tricks

Did you know that it’s possible to use a pre-allocated slice without specifying the length of the array (zero). This allows us to use append just like we would:

// instead of
a := make([]int, 10)
a[0] = 1

// use this
b := make([]int, 0, 10)
b = append(b, 1)
βž–βž–βž–βž–βž–βž–βž–βž–
πŸ•Š @gopher_academy | @GolangEngineers
πŸ”₯1πŸ•Š1🍾1πŸ’Š1
Chaining technique

#tips #tricks

The technique of chaining can be applied to function (pointer) receivers. To illustrate this, let’s consider a Person struct with two functions, AddAge and Rename, that can be used to modify it.

type Person struct {
Name string
Age int
}

func (p *Person) AddAge() {
p.Age++
}

func (p *Person) Rename(name string) {
p.Name = name
}
If you’re looking to add age to a person and then rename them, the typical approach is as follows:

func main() {
p := Person{Name: "Aiden", Age: 30}

p.AddAge()
p.Rename("Aiden 2")
}
Alternatively, we can modify the AddAge and Rename function receivers to return the modified object itself, even if they don’t typically return anything.

func (p *Person) AddAge() *Person {
p.Age++
return p
}

func (p *Person) Rename(name string) *Person {
p.Name = name
return p
}
By returning the modified object itself, we can easily chain multiple function receivers together without having to add unnecessary lines of code:

p = p.AddAge().Rename("Aiden 2")
βž–βž–βž–βž–βž–βž–βž–βž–
πŸ•Š @gopher_academy | @GolangEngineers
πŸ‘8πŸ”₯2🍾2
Using import with β€˜_’ for package initialization

#tips #tricks

Sometimes, in libraries, you may come across import statements that combine an underscore (_) like this:

import ( _ "google.golang.org/genproto/googleapis/api/annotations" )
This will execute the initialization code (init function) of the package, without creating a name reference for it. This allows you to initialize packages, register connections, and perform other tasks before running the code.

Let’s consider an example to better understand how it works:

// underscore
package underscore

func init() {
fmt.Println("init called from underscore package")
}
// mainpackage main
import (
_ "lab/underscore"
)
func main() {}
// log: init called from underscore package
βž–βž–βž–βž–βž–βž–βž–βž–
πŸ•Š @gopher_academy | @GolangEngineers
πŸ‘5🍾2πŸ”₯1
Use import with dot .

#tips #tricks

Having explored how we can use import with underscore, let’s now look at how the dot . operator is more commonly used.

As a developer, the dot . operator can be used to make the exported identifiers of an imported package available without having to specify the package name, which can be a helpful shortcut for lazy developers.

Pretty cool, right? This is especially useful when dealing with long package names in our projects, such as externalmodel or doingsomethinglonglib

To demonstrate, here’s a brief example:

package main

import (
"fmt"
. "math"
)

func main() {
fmt.Println(Pi) // 3.141592653589793
fmt.Println(Sin(Pi / 2)) // 1
}
βž–βž–βž–βž–βž–βž–βž–βž–
πŸ•Š @gopher_academy | @GolangEngineers
πŸ‘8🍾2πŸ’Š1
Ternary with generic

#tips #tricks

Go does not have built-in support for ternary operators like many other programming languages:

# python 
min = a if a < b else b

# c#
min = x < y ? x : y
With Go’s generics in version 1.18, we now have the ability to create a utility that allows for ternary-like functionality in just a single line of code:

// our utility
func Ter[T any](cond bool, a, b T) T {
if cond {
return a
}

return b
}

func main() {
fmt.Println(Ter(true, 1, 2)) // 1
fmt.Println(Ter(false, 1, 2)) // 2
}

βž–βž–βž–βž–βž–βž–βž–βž–
πŸ•Š @gopher_academy | @GolangEngineers
πŸ‘8🍾3
Synchronize a map

#tips #tricks

To synchronize a map, you will see code that uses a sync.Mutex or sync.RWMutex.

You gain some benefits using a sync.RWMutex with read-heavy operations on the map.

var (
s map[int]bool
m sync.RWMutex
)

for i := 0; i < 7; i++ {
go func(i int) {
m.Lock()
defer m.Unlock()
s[i] = true
}(i)
}

// Elsewhere
m.RLock()
for k, v := range s {
fmt.Println(k, v)
}
m.RUnlock()
Instead of using a map mutex pair, you can use sync.Map:

var s sync.Map

for i := 0; i < 7; i++ {
go func(i int) {
s.Store(i, true)
}(i)
}

// Elsewhere
s.Range(func(k, v any) bool {
fmt.Println(k.(int), v.(bool))
return true
})
If you feel uneasy about the use of any in all sync.Map functions, you could define a generic wrapper:

type Map[K any, V any] struct {
m sync.Map
}

func (m *Map[K, V]) Load(key K) (V, bool) {
v, ok := m.m.Load(key)
return v.(V), ok
}

func (m *Map[K, V]) Range(fn func(key K, value V) bool) {
m.m.Range(func(key, value any) bool {
return fn(key.(K), value.(V))
})
}

func (m *Map[K, V]) Store(key K, value V) {
m.m.Store(key, value)
}
And then use the wrapper instead:

var s Map[int, bool]

for i := 0; i < 7; i++ {
go func(i int) {
s.Store(i, true)
}(i)
}

// Elsewhere
s.Range(func(k int, v bool) bool {
fmt.Println(k, v)
return true
})
One caveat is that the Range function is different from holding a lock around the range loop in the sync.RWMutex example. Range does not necessarily correspond to any consistent snapshot of the map’s contents.

βž–βž–βž–βž–βž–βž–βž–βž–
πŸ•Š @gopher_academy | @GolangEngineers
❀6πŸ’Š2