Go is a statically typed, compiled programming language designed at Google in 2007 to improve programming productivity for multicore and networked machines. It addresses criticisms of other languages used at Google while keeping useful characteristics like C's performance, Python's readability, and support for high-performance networking and multiprocessing. Go is syntactically similar to C but adds memory safety, garbage collection, and CSP-style concurrency. There are two major implementations that target multiple platforms including WebAssembly. Go aims to guarantee that code written for one version will continue to build and run with future versions.
Go is a statically typed, compiled language by Google for productivity in multicore systems, with features like static typing, readability, and concurrency.
Basic syntax for printing 'Hello, Go!' using the main package and fmt library.
Examples of variable declarations in Go using var, :=, and const, showcasing various data types.
Definition of functions with multiple return values and basic function examples for sum and reversing strings.
Introduction to zero values in Go for different data types like int, float64, bool, and string.
Examples showing exported and unexported variables and functions in Go, highlighting visibility.
Implementation of switch statements in Go to handle operating system checks and other conditional flows.
Explanation of pointers, demonstrating how to read and modify values using pointer variables.
Defines structs in Go, providing examples of creating a Vertex structure and implementing a constructor.
Demonstrates array usage in Go with examples of initialization and output of string and integer arrays.
Introduction to slices as flexible data structures, explaining length, capacity, and initialization.
Examples of manipulating slices showing calculations and utilizing range in loops.
Map usage in Go for key-value pairs, showing initialization and simple operations with Vertex struct.
Defines methods associated with structs in Go and how they can manipulate struct data.
Explains the difference between pointer and value receivers in methods for efficiency.
Defines enums using iota, showcasing a simple account status enum and its string representation.
Introduction to interfaces in Go, defining collections of method signatures for types.
Shows how structs can implicitly implement interfaces, useful for testing and polymorphism.
Describes struct embedding for type composition instead of inheritance, providing an authenticated request example.
Explains the empty interface, which can hold any type, emphasizing its flexibility.
Overview of import conventions in Go, including aliases and single-use imports.
Brief mention of tools available for Go programming.
Discusses panic scenarios in Go for error handling and program termination.
Explains the use of defer to delay function execution until the surrounding function returns.
Introduces recover function for handling panics, preventing program termination.
Error handling strategies in Go using the error interface, with return values and examples.
Shows how to manage stack traces in error handling approaches to maintain cleaner error reports.
Draft ideas for improving error handling in Go 2.0, influenced by other languages.
Describes go.mod for managing modules, versions, and effective caching in Go projects.
Provides commands to tidy and vendor dependencies for better project management.
Introduces generics with type parameters in Go, allowing flexible data structure operations.
Explains how Context is used for managing request deadlines and cancellation across APIs.
Illustrates a unit test case example for testing functionality in Go programs.
Describes the mocking technique for interfaces in unit testing, enhancing testability.
Example of how to pass additional data with context using WithValue.
Demonstrates context cancellation and its effect on goroutines.
Explains deadline and timeout contexts and how they manage execution flows.
Discusses the use of context in OpenTelemetry for keeping track of requests across systems.
Describes the principles of concurrency vs. parallelism in Go, as explained by Rob Pike.
Introduces goroutines, lightweight threads in Go controlled by the runtime for efficient execution.
Explains the purpose of channels in facilitating safe communication between goroutines.
Describes buffered channels, their usage, and the importance of closing channels appropriately.
Explains how to enforce channel directions for increased type safety in function parameters.
Introduces select statement for managing multiple channel operations concurrently.
Demonstrates how to implement timeouts using the select statement and time.After.
Introduces wait groups to synchronize completion of multiple goroutines.
Describes the use of Mutex for preventing concurrent access issues in shared variables.
Explains atomic operations and their usage with goroutines for thread-safe counter increments.
Introduces fuzz testing available in Go 1.18 for automated input manipulation to find bugs.
Highlights key best practices for writing effective Go code, particularly error handling.
Discusses the issues related to package-level variables and magic state, advocating against them.
Lists desired improvements in Go, including features for better error handling and functional programming.
Provides various links for further learning and understanding of Go programming language.
Go is astatically typed, compiled programming language designed at Google in 2007 by Robert Griesemer(JVM, V8 JavaScript
engine),
Rob Pike(Unix, UTF-8), and Ken Thompson(B, C, Unix) to improve programming productivity in an era
of multicore, networked machines and large codebases.
The designers wanted to address criticism of other languages in use at Google, but keep their useful characteristics:
- Static typing and run-time efficiency (like C)
- Readability and usability (like Python or JavaScript)
- High-performance networking and multiprocessing
It is syntactically like C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.
There are two major implementations:
1. Google's self-hosting "gc" compiler toolchain, targeting multiple operating systems and WebAssembly.
2. gofrontend, a frontend to other compilers, with the libgo library. With GCC the combination is gccgo; with LLVM the combination is
gollvm.
A third-party source-to-source compiler, GopherJS,[20] compiles Go to JavaScript for
front-end web development.
The guarantee: code written for Go 1.0 will build and run with Go 1.X.
Zero Values
var iint // my zero value is 0
var f float64 // my zero value is 0
var b bool // my zero value is false
var s string // my zero value is ""
Switch
switch os :=runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.n", os)
}
// Switch with no condition
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
9.
Pointers
func main() {
i,j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i
p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
A pointer holds the memory address of a value.
10.
Struct (“class”)
type Vertexstruct {
X int
Y int
}
func NewVertex(x, y int) *Vertex{
return &Vertex{
X: x,
Y: y,
}
}
A struct is a collection of fields
Slices(“Lists”)
Slices are pointersto arrays, with the length of the
segment, and its capacity.
The length of a slice is the number of elements it contains.
The capacity of a slice is the number of elements in the
underlying array, counting from the first element in the
slice.
a := make([]int, 5) // len(a)=5
var s []int
// append works on nil slices.
s = append(s, 0)
What is the zero value of a slice?
13.
Slices(“Lists”)
var pow =[]int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %dn", i, v)
}
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Printf("%dn", value)
}
}
14.
Maps
type Vertex struct{
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
What is the zero value of a map?
15.
Methods
Go does nothave classes. However, you can define methods on types.
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
16.
Methods
Choosing a valueor pointer receiver
There are two reasons to use a pointer receiver.
The first is so that the method can modify the value that its receiver points to.
The second is to avoid copying the value on each method call.
This can be more efficient if the receiver is a large struct, for example.
In general, all methods on a given type should have either value or pointer
receivers, but not a mixture of both.
17.
Enums
type AccountStatus int
const(
CreateInProgress AccountStatus = iota
CreateDone
CreateFailed
DeleteInProgress
DeleteFailed
)
func (a AccountStatus) String() string {
return [...]string{
"CreateInProgress",
"CreateDone",
"CreateFailed",
"DeleteInProgress",
"DeleteFailed",
}[a]
}
iota is an identifier that is used with constant, and which can
simplify constant definitions that use auto-increment
numbers. The iota keyword represents integer constant
starting from zero.
18.
Interfaces
type Abser interface{
Abs() float64
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
* Interface are pointers in GO!
Interfaces are named collections of method signatures.
19.
Interfaces “trick”!
Let's saywe are using a third-party library that defines this struct:
type Vertex struct {}
func (v *Vertex) DoSmt() float64 {
return 1.1
}
We can create an interface in our code and make Vertex struct implicity implement our interace!
It might be very usfull for unit testing.
type Bla interface {
DoSmt() float64
}
20.
Composition(Embedding) over inheritance
typeRequest struct {
Resource string
}
type AuthenticatedRequest struct {
Request
Username, Password string
}
func main() {
ar := new(AuthenticatedRequest)
ar.Resource = "example.com/request"
ar.Username = "bob"
ar.Password = "P@ssw0rd"
fmt.Printf("%#v", ar)
}
Go supports embedding of structs and interfaces to
express a more seamless composition of types.
21.
The empty interface(Any/Object)
The interface type that specifies zero methods is known as the
empty interface: interface{}
An empty interface may hold values of any type. (Every type implements at least zero
methods.)
22.
Imports
1. Default import
2.Using alias
3. Access package content without typing the package name
4. Import a package solely for its side-effact (initialization)
import "fmt"
import format "fmt"
import . "fmt"
import _ "fmt"
Panic
A panic typicallymeans something went unexpectedly wrong. Mostly we use it to fail
fast on errors that shouldn’t occur during normal operation, or that we aren’t
prepared to handle gracefully.
Running this program will cause it to panic, print an error message and goroutine
traces, and exit with a non-zero status.
func main() {
panic("a problem")
}
25.
Defer
A defer statementdefers the execution of a function until the
surrounding function returns.
When to use?
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
26.
Recover
Go makes itpossible to recover from a panic, by using
the recover built-in function. A recover can stop a panic from aborting
the program and let it continue with execution instead.
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered. Error:n", r)
}
}()
mayPanic()
fmt.Println("After mayPanic()")
}
27.
Error handling
Error handlingin Golang is done through the built-in interface type, error.
It’s zero value is nil; so, if it returns nil, that means that there were no error.
By convention, errors are the last return value
func divide(x int, y int) (int, error) {
if y == 0 {
return -1, errors.New("Cannot divide by 0!")
}
return x/y, nil
}
func main() {
answer, err := divide(5,0)
if err != nil {
// Handle the error!
fmt.Println(err) // will print “Cannot divide by 0!”
} else {
// No errors!
fmt.Println(answer)
}
}
type error interface {
Error() string
}
28.
Error handling –where is my stacktrace?
func FindUser(username string) (*db.User, error) {
u, err := db.Find(username)
if err != nil {
return nil, fmt.Errorf("FindUser: failed executing db query: %w", err)
}
return u, nil
}
func SetUserAge(u *db.User, age int) error {
if err := db.SetAge(u, age); err != nil {
return fmt.Errorf("SetUserAge: failed executing db update: %w", err)
}
}
func FindAndSetUserAge(username string, age int) error {
var user *User
var err error
user, err = FindUser(username)
if err != nil {
return fmt.Errorf("FindAndSetUserAge: %w", err)
}
if err = SetUserAge(user, age); err != nil {
return fmt.Errorf("FindAndSetUserAge: %w", err)
}
return nil
}
Calling FindAndSetUserAge result:
FindAndSetUserAge: SetUserAge: failed executing db update: malformed request
Golang authors thought that the stack trace should be printed
only on Panic to reduce the overhead of unwinding the call stack.
It goes hand in hand with the error handling approach -
since errors must be handled, the developer should take care also
for the stack trace.
However, if you still want a stack trace you can get it using go runtime/debug package.
There are some third-party libraries that can be used for that:
https://pkg.go.dev/golang.org/x/xerrors
https://github.com/pkg/errors
https://github.com/rotisserie/eris
Dependecies (Go Modules)
Thego.mod file is the root of dependency management in GoLang.
All the modules which are needed or to be used in the project are
maintained in go.mod file.
After running any package building command like go build, go test for
the first time, it will install all the packages with specific versions i.e. which
are the latest at that moment.
It will also create a go.sum file which maintains the checksum so when you
run the project again it will not install all packages again. But use the cache
which is stored inside $GOPATH/pkg/mod directory
(module cache directory).
go.sum is a generated file you don’t have to edit or modify this file.
“require” will include all dependency modules and the related version we
are going to use in our project
“replace” points to the local version of a dependency in Go rather than the
git-web. It will create a local copy of a vendor with versions available so no
need to install every time when we want to refer the vendor.
“//indirect” implies that we are not using these dependencies inside our
project but there is some module which imports these.
all the transitive dependencies are indirect, these include dependencies
which our project needs to work properly.
31.
Dependecies (Go Modules)
gomod tidy -
It will bind the current imports in the project and packages listed in go.mod.
It ensures that the go.mod file matches the source code in the module. It adds any missing
module requirements necessary to build the current module’s packages and dependencies, if
there are some not used dependencies go mod tidy will remove those from go.mod accordingly.
It also adds any missing entries to go.sum and removes unnecessary entries.
go mod vendor -
It generates a vendor directory with the versions available. It copies all third-party dependencies
to a vendor folder in your project root.
This will add all the transitive dependencies required in order to run the vendor package.
When vendoring is enabled, the go command will load packages from the vendor directory
instead of downloading modules from their sources into the module cache and using packages
those downloaded.
32.
Generics
Starting with version1.18, Go has added support for generics, also known
as type parameters.
func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
func main() {
var m = map[int]string{1: "2", 2: "4", 4: "8"}
fmt.Println("keys:", MapKeys(m))
}
33.
Context
A Context carriesdeadlines, cancellation signals, and other request-scoped values across API boundaries and
goroutines.
Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context.
The chain of function calls between them must propagate the Context, optionally replacing it with a derived
Context created using WithCancel, WithDeadline, WithTimeout, or WithValue.
When a Context is canceled, all Contexts derived from it are also canceled.
The WithCancel, WithDeadline, and WithTimeout functions take a Context (the parent) and return a derived
Context (the child) and a CancelFunc. Calling the CancelFunc cancels the child and its children, removes the
parent's reference to the child, and stops any associated timers. Failing to call the CancelFunc leaks the child
and its children until the parent is canceled or the timer fires.
The go vet tool checks that CancelFuncs are used on all control-flow paths.
- Do not pass a nil Context, even if a function permits it.
Pass context.TODO if you are unsure about which Context to use.
- Use context Values only for request-scoped data that transits processes and APIs,
not for passing optional parameters to functions.
- The same Context may be passed to functions running in different goroutines;
Contexts are safe for simultaneous use by multiple goroutines (context is immutable)
Unit Testing -Mocking
type MyRepo interface {
Get(ctx context.Context, id string) (interface{}, error)
Update(ctx context.Context, model interface{}) (interface{}, error)
}
type MyRepoImpl struct {
pg interface{}
}
func (m *MyRepoImpl) Get(ctx context.Context, id string) (interface{}, error) {
panic("implement me")
}
func (m *MyRepoImpl) Update(ctx context.Context, model interface{}) (interface{}, error) {
panic("implement me")
}
type MyRepoMock struct {
MockGet func(ctx context.Context, id string) (interface{}, error)
MockUpdate func(ctx context.Context, model interface{}) (interface{}, error)
}
func (a *MyRepoMock) Get(ctx context.Context, id string) (interface{}, error) {
return a.MockGet(ctx, id)
}
func (a *MyRepoMock) Update(ctx context.Context, model interface{}) (interface{}, error) {
return a.MockUpdate(ctx, model)
}
type MyService struct {
myRepo MyRepo
}
func (s *MyService) DoSmt(ctx context.Context) error {
// some logic...
model, err := s.myRepo.Get(ctx, "123")
if err != nil {
return err
}
// some logic...
}
36.
Context - WithValue
funcdoSomething(ctx context.Context) {
fmt.Printf("doSomething: myKey's value is %sn", ctx.Value("myKey"))
}
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "myKey", "myValue")
doSomething(ctx)
}
Output
doSomething: myKey's value is myValue
37.
Context - WithCancel
funcdoSomething(ctx context.Context) {
ctx, cancelCtx := context.WithCancel(ctx)
printCh := make(chan int)
go doAnother(ctx, printCh)
for num := 1; num <= 3; num++ {
printCh <- num
}
cancelCtx()
time.Sleep(100 * time.Millisecond)
fmt.Printf("doSomething: finishedn")
}
func doAnother(ctx context.Context, printCh <-chan int) {
for {
select {
case <-ctx.Done():
if err := ctx.Err(); err != nil {
fmt.Printf("doAnother err: %sn", err)
}
fmt.Printf("doAnother: finishedn")
return
case num := <-printCh:
fmt.Printf("doAnother: %dn", num)
}
}
}
Output
doAnother: 1
doAnother: 2
doAnother: 3
doAnother err: context canceled
doAnother: finished
doSomething: finished
38.
Context – WithDeadline/ WithTimeout
const shortDuration = 1 * time.Millisecond
func main() {
d := time.Now().Add(shortDuration)
ctx, cancel := context.WithDeadline(context.Background(), d)
// Even though ctx will be expired, it is good practice to call its
// cancellation function in any case. Failure to do so may keep the
// context and its parent alive longer than necessary.
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
Output:
context deadline exceeded
39.
Context – OpenTelemetry
OpenTelemetryalso relies heavily on context for what is
called Context Propagation. That is a way to tied up
requests happening in different systems. The way to
implement that is to Inject span information into the
context you are going to send as part of the protocol you
are using (HTTP or gRPC, for instance). On the other
service you need to Extract the span information out of the
context.
40.
Concurrency model
According toRob Pike, concurrency is the composition of independently
executing computations, and concurrency is not parallelism: concurrency
is about dealing with lots of things at once, but parallelism is about doing
lots of things at once. Concurrency is about structure, parallelism is
about execution, concurrency provides a way to structure a solution to
solve a problem that may (but not necessarily) be parallelizable.
If you have only one processor, your program can still be concurrent, but it
cannot be parallel.
On the other hand, a well-written concurrent program might run efficiently in
parallel on a multiprocessor.
41.
Goroutine
A goroutine isa lightweight thread(faster context switching & smaller size(2kB) compared with OS thread(1MB))
managed by the Go runtime.
It has its own call stack, which grows and shrinks as required.
It's very cheap. It's practical to have thousands, even hundreds of thousands of goroutines.
It's not a thread!
There might be only one thread in a program with thousands of goroutines.
Instead, goroutines are multiplexed dynamically onto threads as needed to keep all the goroutines running.
But if you think of it as a very cheap thread, you won't be far off.
The GOMAXPROCS variable limits the number of operating system threads
that can execute user-level Go code simultaneously.
There is no limit to the number of threads that can be blocked in system calls on behalf of Go code;
those do not count against the GOMAXPROCS limit. This package's GOMAXPROCS function
queries and changes the limit.
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
42.
Channels
Don't communicate bysharing memory, share memory by communicating!
A channel in Go provides a connection between two goroutines, allowing them to communicate.
// Declaring and initializing.
var c chan int
c = make(chan int)
// or
c := make(chan int)
// Sending on a channel.
c <- 1
// Receiving from a channel.
// The "arrow" indicates the direction of data flow.
value = <-c
43.
Channels
Buffered Channels -Sends to a buffered channel block only when the buffer is full.
Receives block when the buffer is empty.
ch := make(chan int, 100)
A sender can close a channel to indicate that no more values will be sent.
Only the sender should close a channel, never the receiver!
Closing is only necessary when the receiver must be told there are no more
values coming!
v, ok := <-ch
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
44.
Channels
Channel Directions- Whenusing channels as function parameters, you can specify if a channel is
meant to only send or receive values. This specificity increases the type-safety of the program.
This ping function only accepts a channel for sending values. It would be a compile-time error to try
to receive on this channel.
The pong function accepts one channel for receives (pings)
and a second for sends (pongs).
func ping(pings chan<- string, msg string) {
pings <- msg
}
func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
}
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
45.
Channels - Select
Theselect statement lets a goroutine wait on multiple
communication operations.
A select blocks until one of its cases can run, then it
executes that case. It chooses one at random if multiple
are ready.
A default clause, if present, executes immediately if no
channel is ready.
select {
case v1 := <-c1:
fmt.Printf("received %v from c1n", v1)
case v2 := <-c2:
fmt.Printf("received %v from c2n", v1)
case c3 <- 23:
fmt.Printf("sent %v to c3n", 23)
default:
fmt.Printf("no one was ready to communicaten")
}
46.
Channels - Timeoutusing select
The time.After function returns a channel that blocks for the
specified duration.
After the interval, the channel delivers the current time, once.
func main() {
c := boring("Joe")
for {
select {
case s := <-c:
fmt.Println(s)
case <-time.After(1 * time.Second):
fmt.Println("You're too slow.")
return
}
}
}
47.
WaitGroups
To wait formultiple goroutines to finish, we can use a wait group.
func worker(id int) {
fmt.Printf("Worker %d startingn", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d donen", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
worker(i)
}(i)
}
wg.Wait()
}
48.
sync.Mutex
We've seen howchannels are great for communication
among goroutines.
But what if we don't need communication? What if we just
want to make sure only one goroutine can access a
variable at a time to avoid conflicts?
This concept is called mutual exclusion, and the
conventional name for the data structure that provides it
is mutex.
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mu.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
49.
Atomic Counters
Here we’lllook at using the sync/atomic package for atomic
counters accessed by multiple goroutines.
func main() {
var ops uint64
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
for c := 0; c < 1000; c++ {
atomic.AddUint64(&ops, 1)
}
wg.Done()
}()
}
wg.Wait()
fmt.Println("ops:", ops)
}
50.
Go Fuzzing
Go supportsfuzzing in its standard toolchain beginning in Go 1.18.
Fuzzing is a type of automated testing which continuously manipulates inputs to a
program to find bugs. Go fuzzing uses coverage guidance to intelligently walk
through the code being fuzzed to find and report failures to the user. Since it can
reach edge cases which humans often miss, fuzz testing can be particularly valuable
for finding security exploits and vulnerabilities.
51.
Best practices
- Donot ignore errors.
- Handle error only once.
- Use Channels for goroutines comunication.
- Return an error instead of using Panic (especially in libs).
- Return an Error interface instead of a custom error struct.
- Do not be lazy; Use interfaces!
- Do not pass function arguments on context.Context.
- Pass context.Context as the first argument to a function.
- Do not use package level vars & func init(magic is bad; global state is magic).
- Handle errors in deferred functions.
- When choosing a third-party lib make sure it is following the GO
Standard lib(context.Context, net/http…).
- Avoid Reflection.
52.
What is theoutput of this program?
var WhatIsThe = AnswerToLife()
func AnswerToLife() int {
println("Hello from AnswerToLife")
return 42
}
func init() {
println("Hello from init")
WhatIsThe = 0
}
func main() {
println("Hello from main")
if WhatIsThe == 0 {
println("It's all a lie.")
}
}
Hello from AnswerToLife
Hello from init
Hello from main
It's all a lie.
Do not use package level vars & func init!
magic is bad; global state is magic!
53.
What would welike Go to be better at?
• Error handling (& stack trace).
• Function Overloading and Default Values for Arguments.
• Extension funcs.
• Get Goroutine “result” – like Kotlin Coroutines:
• Null safety
• Define Enum type
• Functional programming
• Macros / inline function
fun String.removeFirstLastChar(): String = this.substring(1, this.length - 1)
val result = "Hello Everyone".removeFirstLastChar()
val x string?
x?.toLowercase()
val deferred: Deferred<String> = async {
"Hello World"
}
val result = deferred.await()