frodocoder

Since
13 Snippets
  • Examples of generics in Go

    package main
    
    import (
    	"fmt"
    	"sort"
    
    	"golang.org/x/exp/constraints"
    )
    
    // Keys retrieves the keys from the map m as a slice.
    // The order of the keys is unpredictable.
    // This function includes two type parameters: K and V.
    // Map keys must be comparable, meaning K must satisfy the
    // predeclared constraint of being comparable. Values in the
    // map can be of any type.
    func Keys[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
    }
    
    // Filter removes values from a slice based on a filter function.
    // It returns a new slice containing only the elements of s
    // for which the function f returns true.
    func Filter[T any](s []T, f func(T) bool) []T {
    	var r []T
    	for _, v := range s {
    		if f(v) {
    			r = append(r, v)
    		}
    	}
    	return r
    }
    
    // Sort sorts a slice of any orderable type T.
    // The constraints.Ordered constraint in the Sort() function ensures
    // that it can sort values of any type that supports the operators <, <=, >=, and >.
    func Sort[T constraints.Ordered](s []T) {
    	sort.Slice(s, func(i, j int) bool {
    		return s[i] < s[j]
    	})
    }
    
    // Define type Number with constraints
    type Number interface {
    	constraints.Float | constraints.Integer
    }
    
    // Multiply number by 2
    func Double[T Number](value T) T {
    	return value * 2
    }
    
    // Sum calculates the total of the values in a map that contains numeric or float values.
    func Sum[K comparable, V Number](m map[K]V) V {
    	var s V
    	for _, v := range m {
    		s += v
    	}
    	return s
    }
    
    // Scale returns a copy of s with each element multiplied by c.
    func Scale[S ~[]E, E constraints.Integer](s S, c E) S {
    	r := make(S, len(s))
    	for i, v := range s {
    		r[i] = v * c
    	}
    	return r
    }
    
    func main() {
    	k := Keys(map[int]int{1: 2, 2: 4})
    	fmt.Println(k)
    
    	sum := Sum(map[int]int{1: 2, 2: 4})
    	fmt.Println(sum)
    
    	s := []int{1, 2, 3, 7, 5, 22, 18}
    
    	evens := Filter(s, func(i int) bool { return i%2 == 0 })
    	fmt.Println(evens)
    
    	Sort(s)
    	fmt.Println(s)
    
    	fmt.Println(Double(23))
    	fmt.Println(Double(23.23))
    
    	intValues := map[string]int64{
    		"first":  23,
    		"second": 565,
    		"third":  755,
    	}
    
    	fmt.Println(Sum(intValues))
    
    	sc := Scale([]int{1, 2, 3}, 2)
    	fmt.Println(sc)
    }

    Generics is a feature that enables you to write reusable and type-safe code. With generics, you can create functions and data structures that operate on multiple types without requiring runtime type assertions or type casting. Here are examples of using generics in Go.

  • Iterators in Go

    package main
    
    import (
    	"fmt"
    	"iter"
    )
    
    // As long as the loop continues to run, yield will keep returning true,
    // indicating that more values exist.
    // However, if the loop terminates (for instance, due to a break or return),
    // yield will return false.
    // This allows to perform any necessary cleanup.
    func generateFibonacci() iter.Seq[int] {
    	return func(yield func(int) bool) {
    		a, b := 1, 1
    
    		for {
    			if !yield(a) {
    				fmt.Println("Stop sequence")
    				return
    			}
    
    			a, b = b, a+b
    		}
    	}
    }
    
    func main() {
    	for v := range generateFibonacci() {
    		fmt.Println(v)
    
    		if v > 10 {
    			break
    		}
    	}
    }
    
    // $ go run main.go 
    // 1
    // 1
    // 2
    // 3
    // 5
    // 8
    // 13
    // Stop sequence

    A Golang iterator is a function that “yields” one result at a time, rather than computing an entire set of results and returning them all at once. When you use an iterator in a for loop with a range expression, the loop executes once for each value returned by the iterator. Here is an example of range over iterator in Go.

  • Copy file in Go

    package main
    
    import (
        "io"
        "fmt"
        "os"
    )
    
    // copy file using os package
    func copyFileUsingOsPackage(src, dst string) error {
        bytesRead, err := os.ReadFile(src)
        if err != nil {
            return err
        }
    
        return os.WriteFile(dst, bytesRead, 0644)
    }
    
    // copy file using io package
    func copyFileUsingIoPackage(src, dst string) (int64, error) {
            source, err := os.Open(src)
            if err != nil {
                    return 0, err
            }
            defer source.Close()
    
            destination, err := os.Create(dst)
            if err != nil {
                    return 0, err
            }
            defer destination.Close()
            
            nBytes, err := io.Copy(destination, source)
            
            return nBytes, err
    }
    
    func main() {
        src := "/tmp/file-src.txt"
        dst := "/tmp/file-dst.txt"
    
        n, err := copyFileUsingIoPackage(src, dst)
    
        if err != nil {
            panic(err)
        }
    
        fmt.Printf("Copied %d bytes", n)
        fmt.Println()
    
        err = copyFileUsingOsPackage(src, dst)
    
        if err != nil {
            panic(err)
        }
    }

    This example shows how to copy a file in Golang.

  • Base64 encoding and decoding in Go

    package main
    
    import (
    	b64 "encoding/base64"
    	"fmt"
    )
    
    func main() {
    	str := "text123321!?$*&()'-=@~"
    
    	// Encode using the standard encoder
    	strEnc := b64.StdEncoding.EncodeToString([]byte(str))
    	fmt.Println(strEnc)
    
    	strDec, err := b64.StdEncoding.DecodeString(strEnc)
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(string(strDec))
    	fmt.Println()
    
    	// Encode using a URL-compatible base64 format
    	strEnc = b64.URLEncoding.EncodeToString([]byte(str))
    	fmt.Println(strEnc)
    
    	strDec, err = b64.URLEncoding.DecodeString(strEnc)
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(string(strDec))
    }
    
    // $ go run main.go
    // dGV4dDEyMzMyMSE/JComKCknLT1Afg==
    // text123321!?$*&()'-=@~
    
    // dGV4dDEyMzMyMSE_JComKCknLT1Afg==
    // text123321!?$*&()'-=@~

    Here is an example of base64 encoding and decoding. Go supports both standard and URL-compatible base64.

  • Generate random hash or token in Go

    package main
    
    import (
    	"crypto/rand"
    	"crypto/sha256"
    	"encoding/hex"
    	"fmt"
    )
    
    func GenerateRandomHash(n int) (string, error) {
    	b := make([]byte, n)
    	_, err := rand.Read(b)
    
    	// Note that err == nil only if we read len(b) bytes.
    	if err != nil {
    		return "", err
    	}
    
    	hasher := sha256.New()
    	hasher.Write(b)
    	sha := hex.EncodeToString(hasher.Sum(nil))
    
    	return sha, nil
    }
    
    func main() {
    	hash, err := GenerateRandomHash(512)
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(hash)
    }
    
    // go run main.go 
    // 34c0fb393623843e56719b5d9d66385a55b4b4d3393187b7b1a76aee46c421c5

    Here is an example of randomly generating a hash or token in Go.

  • SHA256 hashes in Go

    package main
    
    import (
        "crypto/sha256"
        "fmt"
    )
    
    func main() {
        str := "some string to calculate hash"
        hash := sha256.New()
    
        hash.Write([]byte(str))
        sum := hash.Sum(nil) // the argument can be used to append to an existing byte slice
    
        fmt.Println(str)
        fmt.Printf("%x\n", sum)
    }
    
    // go run main.go 
    // some string to calculate hash
    // 7aad383b9ad516fa67057adc283ce2cf71858aff317a5e267adebfdbd5dda5fd

    SHA256 hashes are commonly used to generate short identifiers for binary or text data. This example shows how to calculate SHA256 hashes for string in Go.

  • Atomic counters in Go

    package main
    
    import (
    	"fmt"
    	"sync"
    	"sync/atomic"
    )
    
    func main() {
    	var atomicCounter, nonAtomicCounter uint64
    	wg := sync.WaitGroup{}
    
    	for i := 0; i < 50; i++ {
    		wg.Add(1)
    
    		go func() { // run 50 goroutines, each goroutine increases the counter 1000 times
    			for c := 0; c < 1000; c++ {
    				atomic.AddUint64(&atomicCounter, 1) // increase atomic counter
    				nonAtomicCounter++ // increase non-atomic counter
    			}
    
    			wg.Done()
    		}()
    	}
    
    	wg.Wait() // wait until all goroutines finish
    	
    	fmt.Println("atomic counter value:", atomicCounter)
    	fmt.Println("non atomic counter value:", nonAtomicCounter)
    }
    
    // go run counters.go 
    // atomic counter value: 50000
    // non atomic counter value: 30648

    An example of using the sync/atomic package for atomic counters accessed by goroutines.

  • Example of timeouts in Go

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	first := make(chan string, 1)
    
    	go func() {
    		time.Sleep(2 * time.Second) // wait 2 seconds before send value to channel
    		first <- "first result"     // send value to the channel
    	}()
    
    	select {
    	case result := <-first:
    		fmt.Println(result)
    	case <-time.After(time.Second): // timeout occurs before the value is read from the first channel
    		fmt.Println("first timeout")
    	}
    
    	second := make(chan string, 1)
    
    	go func() {
    		time.Sleep(time.Second)   // wait 1 second before send value to the channel
    		second <- "second result" // send value to the channel
    	}()
    
    	select {
    	case result := <-second: // the value from the channel is read before the timeout expires
    		fmt.Println(result)
    	case <-time.After(2 * time.Second):
    		fmt.Println("second timeout")
    	}
    }
    
    // $ go run timeouts.go 
    // first timeout
    // second result

    Timeouts are important for programs that connect to external resources or need to limit their execution time.

  • Example of using Go tickers

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	ticker := time.NewTicker(time.Second)
    	fmt.Println("ticker is started")
    
    	done := make(chan struct{})
    
    	go func() {
    		for {
    			select {
    			case <-done:
    				return
    			case t := <-ticker.C:
    				fmt.Println("tick", t)
    			}
    		}
    	}()
    
    	time.Sleep(5 * time.Second)
    
    	ticker.Stop()
    	fmt.Println("ticker is stopped")
    
    	time.Sleep(5 * time.Second)
    	done <- struct{}{}
    }
    
    // go run tickers.go 
    // ticker is started
    // tick 2024-02-09 23:40:25.809616086 +0700 +07 m=+1.000091237
    // tick 2024-02-09 23:40:26.809742343 +0700 +07 m=+2.000152938
    // tick 2024-02-09 23:40:27.809804037 +0700 +07 m=+3.000214622
    // tick 2024-02-09 23:40:28.809852444 +0700 +07 m=+4.000263029
    // ticker is stopped
    // tick 2024-02-09 23:40:29.809908932 +0700 +07 m=+5.000319557
    

    Tickers allow you to repeat actions at certain intervals. A Ticker holds a channel that delivers "ticks" of a clock at intervals. Tickers can be stopped in the same way as timers. When the ticker is stopped, it will no longer be able to accept values ​​into its channel.

  • Example of using Go timers

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	firstTimer := time.NewTimer(3 * time.Second)
    	value := <-firstTimer.C // value is a current time
    
    	fmt.Printf("first timer expired: %v\n", value) // the line printed 3 seconds after running code
    
    	secondTimer := time.NewTimer(2 * time.Second)
    
    	go func() {
    		<-secondTimer.C
    		fmt.Println("second timer expired") // the line is not printed because the second timer is stopped before expiration
    	}()
    
    	stop := secondTimer.Stop() // stop second timer before it expired
    
    	if stop {
    		fmt.Println("second timer is stopped")
    	}
    }
    
    // $ go run timers.go
    // first timer expired: 2024-02-09 22:32:57.570649221 +0700 +07 m=+3.002054417
    // second timer is stopped

    Timers allow you to execute one event in the future. You tell the timer how long you want to wait and it provides a channel to be notified at that time. The first timer will wait for 3 seconds. <-firstTimer.C blocks the timer C channel until a message (current time) is sent indicating that the timer has expired. If you just want to wait, you can use time.Sleep. One reason a timer can be useful is that you can cancel the timer before it expires. The first timer expires 3s after the program starts, but the second is stopped before it expires.