One place for hosting & domains

      Package

      Understanding Package Visibility in Go


      Introduction

      When creating a package in Go, the end goal is usually to make the package accessible for other developers to use, either in higher order packages or whole programs. By importing the package, your piece of code can serve as the building block for other, more complex tools. However, only certain packages are available for importing. This is determined by the visibility of the package.

      Visibility in this context means the file space from which a package or other construct can be referenced. For example, if we define a variable in a function, the visibility (scope) of that variable is only within the function in which it was defined. Similarly, if you define a variable in a package, you can make it visible to just that package, or allow it to be visible outside the package as well.

      Carefully controlling package visibility is important when writing ergonomic code, especially when accounting for future changes that you may want to make to your package. If you need to fix a bug, improve performance, or change functionality, you’ll want to make the change in a way that won’t break the code of anyone using your package. One way to minimize breaking changes is to allow access only to the parts of your package that are needed for it to be used properly. By limiting access, you can make changes internally to your package with less of a chance of affecting how other developers are using your package.

      In this article, you will learn how to control package visibility, as well as how to protect parts of your code that should only be used inside your package. To do this, we will create a basic logger to log and debug messages, using packages with varying degrees of item visibility.

      Prerequisites

      To follow the examples in this article, you will need:

      .
      ├── bin 
      │ 
      └── src
          └── github.com
              └── gopherguides
      

      Exported and Unexported Items

      Unlike other program languages like Java and Python that use access modifiers such as public, private, or protected to specify scope, Go determines if an item is exported and unexported through how it is declared. Exporting an item in this case makes it visible outside the current package. If it’s not exported, it is only visible and usable from within the package it was defined.

      This external visibility is controlled by capitalizing the first letter of the item declared. All declarations, such as Types, Variables, Constants, Functions, etc., that start with a capital letter are visible outside the current package.

      Let’s look at the following code, paying careful attention to capitalization:

      greet.go

      package greet
      
      import "fmt"
      
      var Greeting string
      
      func Hello(name string) string {
          return fmt.Sprintf(Greeting, name)
      }
      

      This code declares that it is in the greet package. It then declares two symbols, a variable called Greeting, and a function called Hello. Because they both start with a capital letter, they are both exported and available to any outside program. As stated earlier, crafting a package that limits access will allow for better API design and make it easier to update your package internally without breaking anyone’s code that is depending on your package.

      Defining Package Visibility

      To give a closer look at how package visibility works in a program, let’s create a logging package, keeping in mind what we want to make visible outside our package and what we won’t make visible. This logging package will be responsible for logging any of our program messages to the console. It will also look at what level we are logging at. A level describes the type of log, and is going to be one of three statuses: info, warning, or error.

      First, within your src directory, let’s create a directory called logging to put our logging files in:

      Move into that directory next:

      Then, using an editor like nano, create a file called logging.go:

      Place the following code in the logging.go file we just created:

      logging/logging.go

      package logging
      
      import (
          "fmt"
          "time"
      )
      
      var debug bool
      
      func Debug(b bool) {
          debug = b
      }
      
      func Log(statement string) {
          if !debug {
              return
          }
      
          fmt.Printf("%s %sn", time.Now().Format(time.RFC3339), statement)
      }
      

      The first line of this code declared a package called logging. In this package, there are two exported functions: Debug and Log. These functions can be called by any other package that imports the logging package. There is also a private variable called debug. This variable is only accessible from within the logging package. It is important to note that while the function Debug and the variable debug both have the same spelling, the function is capitalized and the variable is not. This makes them distinct declarations with different scopes.

      Save and quit the file.

      To use this package in other areas of our code, we can import it into a new package. We’ll create this new package, but we’ll need a new directory to store those source files in first.

      Let’s move out of the logging directory, create a new directory called cmd, and move into that new directory:

      Create a file called main.go in the cmd directory we just created:

      Now we can add the following code:

      cmd/main.go

      package main
      
      import "github.com/gopherguides/logging"
      
      func main() {
          logging.Debug(true)
      
          logging.Log("This is a debug statement...")
      }
      

      We now have our entire program written. However, before we can run this program, we’ll need to also create a couple of configuration files for our code to work properly. Go uses Go Modules to configure package dependencies for importing resources. Go modules are configuration files placed in your package directory that tell the compiler where to import packages from. While learning about modules is beyond the scope of this article, we can write just a couple lines of configuration to make this example work locally.

      Open the following go.mod file in the cmd directory:

      Then place the following contents in the file:

      go.mod

      module github.com/gopherguides/cmd
      
      replace github.com/gopherguides/logging => ../logging
      

      The first line of this file tells the compiler that the cmd package has a file path of github.com/gopherguides/cmd. The second line tells the compiler that the package github.com/gopherguides/logging can be found locally on disk in the ../logging directory.

      We’ll also need a go.mod file for our logging package. Let’s move back into the logging directory and create a go.mod file:

      • cd ../logging
      • nano go.mod

      Add the following contents to the file:

      go.mod

      module github.com/gopherguides/logging
      

      This tells the compiler that the logging package we created is actually the github.com/gopherguides/logging package. This makes it possible to import the package in our main package with the following line that we wrote earlier:

      cmd/main.go

      package main
      
      import "github.com/gopherguides/logging"
      
      func main() {
          logging.Debug(true)
      
          logging.Log("This is a debug statement...")
      }
      

      You should now have the following directory structure and file layout:

      ├── cmd
      │   ├── go.mod
      │   └── main.go
      └── logging
          ├── go.mod
          └── logging.go
      

      Now that we have all the configuration completed, we can run the main program from the cmd package with the following commands:

      You will get output similar to the following:

      Output

      2019-08-28T11:36:09-05:00 This is a debug statement...

      The program will print out the current time in RFC 3339 format followed by whatever statement we sent to the logger. RFC 3339 is a time format that was designed to represent time on the internet and is commonly used in log files.

      Because the Debug and Log functions are exported from the logging package, we can use them in our main package. However, the debug variable in the logging package is not exported. Trying to reference an unexported declaration will result in a compile-time error.

      Add the following highlighted line to main.go:

      cmd/main.go

      package main
      
      import "github.com/gopherguides/logging"
      
      func main() {
          logging.Debug(true)
      
          logging.Log("This is a debug statement...")
      
          fmt.Println(logging.debug)
      }
      

      Save and run the file. You will receive an error similar to the following:

      Output

      . . . ./main.go:10:14: cannot refer to unexported name logging.debug

      Now that we have seen how exported and unexported items in packages behave, we will next look at how fields and methods can be exported from structs.

      Visibility Within Structs

      While the visibility scheme in the logger we built in the last section may work for simple programs, it shares too much state to be useful from within multiple packages. This is because the exported variables are accessible to multiple packages that could modify the variables into contradictory states. Allowing the state of your package to be changed in this way makes it hard to predict how your program will behave. With the current design, for example, one package could set the Debug variable to true, and another could set it to false in the same instance. This would create a problem since both packages that are importing the logging package are affected.

      We can make the logger isolated by creating a struct and then hanging methods off of it. This will allow us to create an instance of a logger to be used independently in each package that consumes it.

      Change the logging package to the following to refactor the code and isolate the logger:

      logging/logging.go

      package logging
      
      import (
          "fmt"
          "time"
      )
      
      type Logger struct {
          timeFormat string
          debug      bool
      }
      
      func New(timeFormat string, debug bool) *Logger {
          return &Logger{
              timeFormat: timeFormat,
              debug:      debug,
          }
      }
      
      func (l *Logger) Log(s string) {
          if !l.debug {
              return
          }
          fmt.Printf("%s %sn", time.Now().Format(l.timeFormat), s)
      }
      

      In this code, we created a Logger struct. This struct will house our unexported state, including the time format to print out and the debug variable setting of true or false. The New function sets the initial state to create the logger with, such as the time format and debug state. It then stores the values we gave it internally to the unexported variables timeFormat and debug. We also created a method called Log on the Logger type that takes a statement we want to print out. Within the Log method is a reference to its local method variable l to get access back to its internal fields such as l.timeFormat and l.debug.

      This approach will allow us to create a Logger in many different packages and use it independently of how the other packages are using it.

      To use it in another package, let’s alter cmd/main.go to look like the following:

      cmd/main.go

      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, true)
      
          logger.Log("This is a debug statement...")
      }
      

      Running this program will give you the following output:

      Output

      2019-08-28T11:56:49-05:00 This is a debug statement...

      In this code, we created an instance of the logger by calling the exported function New. We stored the reference to this instance in the logger variable. We can now call logging.Log to print out statements.

      If we try to reference an unexported field from the Logger such as the timeFormat field, we will receive a compile-time error. Try adding the following highlighted line and running cmd/main.go:

      cmd/main.go

      
      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, true)
      
          logger.Log("This is a debug statement...")
      
          fmt.Println(logger.timeFormat)
      }
      

      This will give the following error:

      Output

      . . . cmd/main.go:14:20: logger.timeFormat undefined (cannot refer to unexported field or method timeFormat)

      The compiler recognizes that logger.timeFormat is not exported, and therefore can’t be retrieved from the logging package.

      Visibility Within Methods

      In the same way as struct fields, methods can also be exported or unexported.

      To illustrate this, let’s add leveled logging to our logger. Leveled logging is a means of categorizing your logs so that you can search your logs for specific types of events. The levels we will put into our logger are:

      • The info level, which represents information type events that inform the user of an action, such as Program started, or Email sent. These help us debug and track parts of our program to see if expected behavior is happening.

      • The warning level. These types of events identify when something unexpected is happening that is not an error, like Email failed to send, retrying. They help us see parts of our program that aren’t going as smoothly as we expected them to.

      • The error level, which means the program encountered a problem, like File not found. This will often result in the program’s operation failing.

      You may also desire to turn on and off certain levels of logging, especially if your program isn’t performing as expected and you’d like to debug the program. We’ll add this functionality by changing the program so that when debug is set to true, it will print all levels of messages. Otherwise, if it’s false, it will only print error messages.

      Add leveled logging by making the following changes to logging/logging.go:

      logging/logging.go

      
      package logging
      
      import (
          "fmt"
          "strings"
          "time"
      )
      
      type Logger struct {
          timeFormat string
          debug      bool
      }
      
      func New(timeFormat string, debug bool) *Logger {
          return &Logger{
              timeFormat: timeFormat,
              debug:      debug,
          }
      }
      
      func (l *Logger) Log(level string, s string) {
          level = strings.ToLower(level)
          switch level {
          case "info", "warning":
              if l.debug {
                  l.write(level, s)
              }
          default:
              l.write(level, s)
          }
      }
      
      func (l *Logger) write(level string, s string) {
          fmt.Printf("[%s] %s %sn", level, time.Now().Format(l.timeFormat), s)
      }
      

      In this example, we introduced a new argument to the Log method. We can now pass in the level of the log message. The Log method determines what level of message it is. If it’s an info or warning message, and the debug field is true, then it writes the message. Otherwise it ignores the message. If it is any other level, like error, it will write out the message regardless.

      Most of the logic for determining if the message is printed out exists in the Log method. We also introduced an unexported method called write. The write method is what actually outputs the log message.

      We can now use this leveled logging in our other package by changing cmd/main.go to look like the following:

      cmd/main.go

      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, true)
      
          logger.Log("info", "starting up service")
          logger.Log("warning", "no tasks found")
          logger.Log("error", "exiting: no work performed")
      
      }
      

      Running this will give you:

      Output

      [info] 2019-09-23T20:53:38Z starting up service [warning] 2019-09-23T20:53:38Z no tasks found [error] 2019-09-23T20:53:38Z exiting: no work performed

      In this example, cmd/main.go successfully used the exported Log method.

      We can now pass in the level of each message by switching debug to false:

      main.go

      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, false)
      
          logger.Log("info", "starting up service")
          logger.Log("warning", "no tasks found")
          logger.Log("error", "exiting: no work performed")
      
      }
      

      Now we will see that only the error level messages print:

      Output

      [error] 2019-08-28T13:58:52-05:00 exiting: no work performed

      If we try to call the write method from outside the logging package, we will receive a compile-time error:

      main.go

      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, true)
      
          logger.Log("info", "starting up service")
          logger.Log("warning", "no tasks found")
          logger.Log("error", "exiting: no work performed")
      
          logger.write("error", "log this message...")
      }
      

      Output

      cmd/main.go:16:8: logger.write undefined (cannot refer to unexported field or method logging.(*Logger).write)

      When the compiler sees that you are trying to reference something from another package that starts with a lowercase letter, it knows that it is not exported, and therefore throws a compiler error.

      The logger in this tutorial illustrates how we can write code that only exposes the parts we want other packages to consume. Because we control what parts of the package are visible outside the package, we are now able to make future changes without affecting any code that depends on our package. For example, if we wanted to only turn off info level messages when debug is false, you could make this change without affecting any other part of your API. We could also safely make changes to the log message to include more information, such as the directory the program was running from.

      Conclusion

      This article showed how to share code between packages while also protecting the implementation details of your package. This allows you to export a simple API that will seldom change for backwards compatibility, but will allow for changes privately in your package as needed to make it work better in the future. This is considered a best practice when creating packages and their corresponding APIs.

      To learn more about packages in Go, check out our Importing Packages in Go and How To Write Packages in Go articles, or explore our entire How To Code in Go series.



      Source link

      Using the context Go package


      Updated by Linode Contributed by Mihalis Tsoukalos

      Go is a compiled, statically typed programming language developed by Google. Many modern applications, including Docker, Kubernetes, and Caddy, are written in Go.

      Running a go command is as simple as:

      go run [filename]
      

      The context package provides contextual information that a goroutine may need such as how long it should run and how and when it should end. It can also pass informational key-value pairs for use down the call chain.

      In this guide you will learn:

      Before You Begin

      You will need to install a recent version of Go on your computer in order to follow the presented commands. Any Go version newer than 1.8 will do but it is considered a good practice to have the latest version of Go installed. You can check your Go version by executing go version.

      If you still need to install Go, you can follow our guide for Ubuntu installation here.

      Note

      This guide is written for a non-root user. Depending on your configuration, some commands might require the help of sudo in order to get property executed. If you are not familiar with the sudo command, see the Users and Groups guide.

      About the context package

      The context package supports both the handling of multiple concurrent operations and the passing of (typically request-scoped) contextual data in key-value pairs.

      If you take a look at the source code of the context package, you will realize that its implementation is pretty simple. The context package defines the Context type, which is a Go interface with four methods, named Deadline(), Done(), Err(), and Value():

      context.go
      1
      2
      3
      4
      5
      6
      
      type Context interface {
          Deadline() (deadline time.Time, ok bool)
          Done() <-chan struct{}
          Err() error
          Value(key interface{}) interface{}
      }
      • The developer will need to declare and modify a Context variable using functions such as context.WithCancel(), context.WithDeadline() and context.WithTimeout().

      • All three of these functions return a derived Context (the child) and a CancelFunc function. Calling the CancelFunc function removes the parent’s reference to the child and stops any associated timers. This means that the Go garbage collector is free to garbage collect the child goroutines that no longer have associated parent goroutines.

      • For garbage collection, the parent goroutine needs to keep a reference to each child goroutine. If a child goroutine ends without the parent knowing about it, then a memory leak occurs until the parent is canceled as well.

      A simple example

      This first code example is relatively simple and illustrates the use of the context.Context type with the help of simple.go.

      Explaining the Go code of the Example

      The code of simple.go is as follows:

      ./simple.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      
      package main
      
      import (
              "context"
              "fmt"
              "os"
              "strconv"
              "time"
      )
      
      // The f1 function creates and executes a goroutine
      // The time.Sleep() call simulates the time it would take a real goroutine
      // to do its job - in this case it is 4 seconds. If the c1 context calls
      // the Done() function in less than 4 seconds, the goroutine will not have
      // enough time to finish.
      func f1(t int) {
              c1 := context.Background()
              // WithCancel returns a copy of parent context with a new Done channel
              c1, cancel := context.WithCancel(c1)
              defer cancel()
      
              go func() {
                      time.Sleep(4 * time.Second)
                      cancel()
              }()
      
              select {
              case <-c1.Done():
                      fmt.Println("f1() Done:", c1.Err())
                      return
              case r := <-time.After(time.Duration(t) * time.Second):
                      fmt.Println("f1():", r)
              }
              return
      }
      
      func f2(t int) {
              c2 := context.Background()
              c2, cancel := context.WithTimeout(c2, time.Duration(t)*time.Second)
              defer cancel()
      
              go func() {
                      time.Sleep(4 * time.Second)
                      cancel()
              }()
      
              select {
              case <-c2.Done():
                      fmt.Println("f2() Done:", c2.Err())
                      return
              case r := <-time.After(time.Duration(t) * time.Second):
                      fmt.Println("f2():", r)
              }
              return
      }
      
      func f3(t int) {
              c3 := context.Background()
              deadline := time.Now().Add(time.Duration(2*t) * time.Second)
              c3, cancel := context.WithDeadline(c3, deadline)
              defer cancel()
      
              go func() {
                      time.Sleep(4 * time.Second)
                      cancel()
              }()
      
              select {
              case <-c3.Done():
                      fmt.Println("f3() Done:", c3.Err())
                      return
              case r := <-time.After(time.Duration(t) * time.Second):
                      fmt.Println("f3():", r)
              }
              return
      }
      
      func main() {
              if len(os.Args) != 2 {
                      fmt.Println("Need a delay!")
                      return
              }
      
              delay, err := strconv.Atoi(os.Args[1])
              if err != nil {
                      fmt.Println(err)
                      return
              }
              fmt.Println("Delay:", delay)
      
              f1(delay)
              f2(delay)
              f3(delay)
      }
      • The program contains four functions including the main() function. Functions f1(), f2(), and f3() each require just one parameter, which is a time delay, because everything else they need is defined inside their functions.

      • In this example we call the context.Background() function to initialize an empty Context. The other function that can create an empty Context is context.TODO() which will be presented later in this guide.

      • Notice that the cancel variable, a function, in f1() is one of the return values of context.CancelFunc(). The context.WithCancel() function uses an existing Context and creates a child with cancellation. The context.WithCancel() function also returns a Done channel that can be closed, either when the cancel() function is called, as shown in the preceding code, or when the Done channel of the parent context is closed.

        Note

        One of the return values of Context.Done() is a Go channel, which means that you will have to use a select statement to work with. Although select looks like switch, select allows a goroutine to wait on multiple communications operations.

      • The cancel variable in f2() comes from context.WithTimeout(). context.WithTimeout() requires two parameters: a Context parameter and a time.Duration parameter. When the timeout period expires, the cancel() function is called automatically.

      • The cancel variable in f3() comes from context.WithDeadline(). context.WithDeadline() requires two parameters: a Context variable and a time in the future that signifies the deadline of the operation. When the deadline passes, the cancel() function is called automatically.

        Note

        Notice that contexts should not be stored in structures – they should be passed as separate parameters to functions. It is considered a good practice to pass them as the first parameter of a function.

      Using simple.go

      Execute simple.go with a delay period of 3 seconds:

      go run simple.go 3
      

      It will generate the following kind of output:

        
      go run simple.go 3
      Delay: 3
      f1(): 2019-05-31 19:29:38.664568 +0300 EEST m=+3.004314767
      f2(): 2019-05-31 19:29:41.664942 +0300 EEST m=+6.004810929
      f3(): 2019-05-31 19:29:44.668795 +0300 EEST m=+9.008786881
      
      

      The long lines of the output are the return values from the time.After() function. They denote normal operation of the program. The point here is that the operation of the program is canceled when there are delays in its execution.

      If you use a bigger delay (10 seconds), which is executed as a call to time.Sleep():

      go run simple.go 10
      

      You will get the following kind of output:

        
      Delay: 10
      f1() Done: context canceled
      f2() Done: context canceled
      f3() Done: context canceled
      
      

      The calls to time.Sleep() simulate a program that is slow or an operation that takes too much time to finish. Production code does not usually have such time.Sleep() function calls.

      Using Context for HTTP

      In this section of the guide you will learn how to timeout HTTP connections on the client side.

      Note

      This example makes a request to a local web server. A suitable, simple web server is available via Python and can be started with the following commands:

      Python 3.X

      python3 -m http.server
      

      Python 2.X

      python -m SimpleHTTPServer
      

      The presented utility, which is called http.go, requires two command line arguments, which are the URL to connect to and the allowed delay value in seconds.

      Explaining the Go Code of the Example

      The Go code of the http.go utility is the following:

      ./http.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      
      package main
      
      import (
              "context"
              "fmt"
              "io/ioutil"
              "net/http"
              "os"
              "strconv"
              "sync"
              "time"
      )
      
      var (
              myUrl string
              delay int = 5
              w     sync.WaitGroup
      )
      
      type myData struct {
              r   *http.Response
              err error
      }
      
      // In packages that use contexts, convention is to pass them as
      // the first argument to a function.
      func connect(c context.Context) error {
              defer w.Done()
              data := make(chan myData, 1)
              tr := &http.Transport{}
              httpClient := &http.Client{Transport: tr}
              req, _ := http.NewRequest("GET", myUrl, nil)
      
              go func() {
                      response, err := httpClient.Do(req)
                      if err != nil {
                              fmt.Println(err)
                              data <- myData{nil, err}
                              return
                      } else {
                              pack := myData{response, err}
                              data <- pack
                      }
              }()
      
              select {
              case <-c.Done():
                      tr.CancelRequest(req)
                      <-data
                      fmt.Println("The request was canceled!")
                      return c.Err()
              case ok := <-data:
                      err := ok.err
                      resp := ok.r
                      if err != nil {
                              fmt.Println("Error select:", err)
                              return err
                      }
                      defer resp.Body.Close()
      
                      realHTTPData, err := ioutil.ReadAll(resp.Body)
                      if err != nil {
                              fmt.Println("Error select:", err)
                              return err
                      }
                      // Although fmt.Printf() is used here, server processes
                      // use the log.Printf() function instead.
                      fmt.Printf("Server Response: %sn", realHTTPData)
              }
              return nil
      }
      
      func main() {
              if len(os.Args) == 1 {
                      fmt.Println("Need a URL and a delay!")
                      return
              }
      
              myUrl = os.Args[1]
              if len(os.Args) == 3 {
                      t, err := strconv.Atoi(os.Args[2])
                      if err != nil {
                              fmt.Println(err)
                              return
                      }
                      delay = t
              }
      
              fmt.Println("Delay:", delay)
              c := context.Background()
              c, cancel := context.WithTimeout(c, time.Duration(delay)*time.Second)
              defer cancel()
      
              fmt.Printf("Connecting to %s n", myUrl)
              w.Add(1)
              go connect(c)
              w.Wait()
              fmt.Println("Exiting...")
      }
      • The timeout period is defined by the context.WithTimeout() method in main().

      • The connect() function that is executed as a goroutine will either terminate normally or when the cancel() function is executed.

        Note

        It is considered a good practice to use context.Background() in the main() function, the init() function of a package or at tests.

      • The connect() function is used for connecting to the desired URL. The connect() function also starts a goroutine before the select block takes control in order to either wait for web data as returned by the goroutine or for a timeout with the help of the Context variable.

      Using http.go

      If the desired delay is too small, then http.go will timeout. One such example is when you declare that you want a delay of 0 seconds, as in the following example:

      go run http.go https://www.linode.com/ 0
      

      The output is as follows:

        
      Delay: 0
      Connecting to https://www.linode.com/
      The request was canceled!
      Exiting...
      
      

      If the timeout period is sufficient, say 10 seconds.

      go run http.go http://localhost:8000 10
      

      Then the output from http.go will be similar to the following:

      Delay: 1
      Connecting to http://localhost:8000
      Server Response: Serving: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
      <html>
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <title>Directory listing for /</title>
      </head>
      <body>
      <h1>Directory listing for /</h1>
      <hr>
      <ul>
      <li><a href="http.go">http.go</a></li>
      <li><a href="more.go">more.go</a></li>
      <li><a href="simple.go">simple.go</a></li>
      </ul>
      <hr>
      </body>
      </html>
      

      Notice that http://localhost:8000 uses a custom made HTTP server that returns a small amount of data. However, nothing prohibits you from trying commands such as:

      go run http.go https://www.linode.com/ 10
      

      Using Contexts as key-value stores

      In this section of the guide you will pass values in a Context and use it as a key-value store. This is a case where we do not pass values into contexts in order to provide further information about why they where canceled.

      The more.go program illustrates the use of the context.TODO() function as well as the use of the context.WithValue() function.

      Explaining the Go Code

      The Go code of more.go is the following:

      ./more.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      
      package main
      
      import (
              "context"
              "fmt"
      )
      
      type aKey string
      
      func searchKey(ctx context.Context, k aKey) {
              v := ctx.Value(k)
              if v != nil {
                      fmt.Println("found value:", v)
                      return
              } else {
                      fmt.Println("key not found:", k)
              }
      }
      
      func main() {
              myKey := aKey("mySecretValue")
              ctx := context.WithValue(context.Background(), myKey, "mySecretValue")
              searchKey(ctx, myKey)
      
              searchKey(ctx, aKey("notThere"))
              emptyCtx := context.TODO()
              searchKey(emptyCtx, aKey("notThere"))
      }
      • This time we create a context using context.TODO() instead of context.Background(). Although both functions return a non-nil, empty Context, their purposes differ. You should never pass a nil context –– use the context.TODO() function to create a suitable context. Use the context.TODO() function when you are not sure about the Context that you want to use.

      • The context.TODO() function signifies that we intend to use an operation context, without being sure about it yet. The good thing is that TODO() is recognized by static analysis tools, which allows them to determine whether a context.Context variable is propagated correctly in a program or not.

      • The context.WithValue() function that is used in main() offers a way to associate a value with a Context`.

      • The searchKey() function retrieves a value from a Context variable and checks whether that value exists or not.

      Using more.go

      Execute more.go with the following command:

      go run more.go
      

      It will generate the following output:

        
      found value: mySecretValue
      key not found: notThere
      key not found: notThere
      
      

      Propagation over HTTP

      In order to share a common context among multiple processes, you will need to propagate that context on your own.

      The logic of this technique is based on the Go code of more.go. First use the context.WithValue() function to add your data into a context, serialize and send over HTTP, decode the data, get the context, and finally use context.Value() to check whether the desired key and desired values are in place or not.

      Note

      The http.Request type has the Context() method that returns the context of the request and the WithContext() method that according to the Go documentation returns a shallow copy of r with its context changed to ctx. You can learn more about both methods at https://golang.org/pkg/net/http/.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      An Introduction to the Strings Package in Go


      Introduction

      Go’s string package has several functions available to work with the string data type. These functions let us easily modify and manipulate strings. We can think of functions as being actions that we perform on elements of our code. Built-in functions are those that are defined in the Go programming language and are readily available for us to use.

      In this tutorial, we’ll review several different functions that we can use to work with strings in Go.

      Making Strings Uppercase and Lowercase

      The functions strings.ToUpper and strings.ToLower will return a string with all the letters of an original string converted to uppercase or lowercase letters. Because strings are immutable data types, the returned string will be a new string. Any characters in the string that are not letters will not be changed.

      Let’s convert the string "Sammy Shark" to be all uppercase:

      ss := "Sammy Shark"
      fmt.Println(strings.ToUpper(ss))
      

      Output

      SAMMY SHARK

      Now, let’s convert the string to be all lowercase:

      fmt.Println(strings.ToLower(ss))
      

      Output

      sammy shark

      Since you are using the strings package, you first need to import it into your program. To convert the string to uppercase and lowercase the entire program would be as follows:

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      func main() {
          ss := "Sammy Shark"
          fmt.Println(strings.ToUpper(ss))
          fmt.Println(strings.ToLower(ss))
      }
      

      The strings.ToUpper and strings.ToLower functions make it easier to evaluate and compare strings by making case consistent throughout. For example, if a user writes their name all lowercase, we can still determine whether their name is in our database by checking it against an all uppercase version.

      String Search Functions

      The strings package has a number of functions that help determine if a string contains a specific sequence of characters.

      Function Use
      strings.HasPrefix Searches the string from the beginning
      strings.HasSuffix Searches the string from the end
      strings.Contains Searches anywhere in the string
      strings.Count Counts how many times the string appears

      The strings.HasPrefix and strings.HasSuffix allow you to check to see if a string starts or ends with a specific set of characters.

      Let’s check to see if the string Sammy Shark starts with Sammy and ends with Shark.

      ss := "Sammy Shark"
      fmt.Println(strings.HasPrefix(ss, "Sammy"))
      fmt.Println(strings.HasSuffix(ss, "Shark"))
      

      Output

      true true

      Let’s check to see if the string Sammy Shark contains the sequence Sh:

      fmt.Println(strings.Contains(ss, "Sh"))
      

      Output

      true

      Finally, let’s see how many times the letter S appears in the phrase Sammy Shark:

      fmt.Println(strings.Count(ss, "S"))
      

      Output

      2

      Note: All strings in Go are case sensitive. This means that Sammy is not the same as sammy.

      Using a lowercase s to get a count from Sammy Shark is not the same as using uppercase S:

      fmt.Println(strings.Count(ss, "s"))
      

      Output

      0

      Because S is different than s, the count returned will be 0.

      String functions are useful when you want to compare or search strings in your program.

      Determining String Length

      The built-in function len() returns the number of characters in a string. This function is useful for when you need to enforce minimum or maximum password lengths, or to truncate larger strings to be within certain limits for use as abbreviations.

      To demonstrate this function, we’ll find the length of a sentence-long string:

      openSource := "Sammy contributes to open source."
      fmt.Println(len(openSource))
      

      Output

      33

      We set the variable openSource equal to the string "Sammy contributes to open source." and then passed that variable to the len() function with len(openSource). Finally we passed the function into the fmt.Println() function so that we could see the program’s output on the screen..

      Keep in mind that the len() function will count any character bound by double quotation marks—including letters, numbers, whitespace characters, and symbols.

      Functions for String Manipulation

      The strings.Join, strings.Split, and strings.ReplaceAll functions are a few additional ways to manipulate strings in Go.

      The strings.Join function is useful for combining a slice of strings into a new single string.

      Let’s create a comma-separated string from a slice of strings:

      fmt.Println(strings.Join([]string{"sharks", "crustaceans", "plankton"}, ","))
      

      Output

      sharks,crustaceans,plankton

      If we want to add a comma and a space between string values in our new string, we can simply rewrite our expression with a whitespace after the comma: strings.Join([]string{"sharks", "crustaceans", "plankton"}, ", ").

      Just as we can join strings together, we can also split strings up. To do this, we use the strings.Split function and split on the spaces:

      balloon := "Sammy has a balloon."
      s := strings.Split(balloon, " ")
      fmt.Println(s)
      

      Output

      [Sammy has a balloon]

      The output is a slice of strings. Since strings.Println was used, it is hard to tell what the output is by looking at it. To see that it is indeed a slice of strings, use the fmt.Printf function with the %q verb to quote the strings:

      fmt.Printf("%q", s)
      

      Output

      ["Sammy" "has" "a" "balloon."]

      Another useful function in addition to strings.Split is strings.Fields. The difference is that strings.Fields will ignore all whitespace, and will only split out the actual fields in a string:

      data := "  username password     email  date"
      fields := strings.Fields(data)
      fmt.Printf("%q", fields)
      

      Output

      ["username" "password" "email" "date"]

      The strings.ReplaceAll function can take an original string and return an updated string with some replacement.

      Let’s say that the balloon that Sammy had is lost. Since Sammy no longer has this balloon, we will change the substring "has" from the original string balloon to "had" in a new string:

      fmt.Println(strings.ReplaceAll(balloon, "has", "had"))
      

      Within the parentheses, first is balloon the variable that stores the original string; the second substring "has" is what we want to be replaced, and the third substring "had" is what we are replacing that second substring with. Our output will look like this:

      Output

      Sammy had a balloon.

      Using the string function strings.Join, strings.Split, and strings.ReplaceAll will provide you with greater control to manipulate strings in Go.

      Conclusion

      This tutorial went through some of the common string package functions for the string data type that you can use to work with and manipulate strings in your Go programs.

      You can learn more about other data types in Understanding Data Types and read more about strings in An Introduction to Working with Strings.



      Source link