One place for hosting & domains

      Functions

      How To Run Serverless Functions Using OpenFaaS on DigitalOcean Kubernetes


      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Typically, hosting a software application on the internet requires infrastructure management, planning, and monitoring for a monolithic system. Unlike this traditional approach, the serverless architecture (also known as function as a service, or FaaS) breaks down your application into functions. These functions are stateless, self contained, event triggered, functionally complete entities that communicate via APIs that you manage, instead of the underlying hardware and explicit infrastructure provisioning. Functions are scalable by design, portable, faster to set up and easier to test than ordinary apps. For the serverless architecture to work in principle, it requires a platform agnostic way of packaging and orchestrating functions.

      OpenFaaS is an open-source framework for implementing the serverless architecture on Kubernetes, using Docker containers for storing and running functions. It allows any program to be packaged as a container and managed as a function via the command line or the integrated web UI. OpenFaaS has excellent support for metrics and provides autoscaling for functions when demand increases.

      In this tutorial, you will deploy OpenFaaS to your DigitalOcean Kubernetes cluster at your domain and secure it using free Let’s Encrypt TLS certificates. You’ll also explore its web UI and deploy existing and new functions using the faas-cli, the official command line tool. In the end, you’ll have a flexible system for deploying serverless functions in place.

      Prerequisites

      • A DigitalOcean Kubernetes cluster with your connection configured as the kubectl default. The cluster must have at least 8GB RAM and 4 CPU cores available for OpenFaaS (more will be required in case of heavier use). Instructions on how to configure kubectl are shown under the Connect to your Cluster step when you create your cluster. To create a Kubernetes cluster on DigitalOcean, see Kubernetes Quickstart.
      • Docker installed on your local machine. Following Steps 1 and 2 for your distribution, see How To Install Docker.
      • An account at Docker Hub for storing Docker images you’ll create during this tutorial.
      • faas-cli, the official CLI tool for managing OpenFaaS, installed on your local machine. For instructions for multiple platforms, visit the official docs.
      • The Helm package manager installed on your local machine. To do this, complete Step 1 and add the stable repo from Step 2 of the How To Install Software on Kubernetes Clusters with the Helm 3 Package Manager tutorial.
      • The Nginx Ingress Controller and Cert-Manager installed on your cluster using Helm in order to expose OpenFaaS using Ingress Resources. For guidance, follow How to Set Up an Nginx Ingress on DigitalOcean Kubernetes Using Helm.
      • A fully registered domain name to host OpenFaaS, pointed at the Load Balancer used by the Nginx Ingress. This tutorial will use openfaas.your_domain throughout. You can purchase a domain name on Namecheap, get one for free on Freenom, or use the domain registrar of your choice.

      Note: The domain name you use in this tutorial must differ from the one used in the “How To Set Up an Nginx Ingress on DigitalOcean Kubernetes” prerequisite tutorial.

      Step 1 — Installing OpenFaaS using Helm

      In this step, you will install OpenFaaS to your Kubernetes cluster using Helm and expose it at your domain.

      As part of the Nginx Ingress Controller prerequisite, you created example Services and an Ingress. You won’t need them in this tutorial, so you can delete them by running the following commands:

      • kubectl delete -f hello-kubernetes-first.yaml
      • kubectl delete -f hello-kubernetes-second.yaml
      • kubectl delete -f hello-kubernetes-ingress.yaml

      Since you’ll be deploying functions as Kubernetes objects, it’s helpful to store them and OpenFaaS itself in separate namespaces in your cluster. The OpenFaaS namespace will be called openfaas, and the functions namespace will be openfaas-fn. Create them in your cluster by running the following command:

      • kubectl apply -f https://raw.githubusercontent.com/openfaas/faas-netes/master/namespaces.yml

      You’ll see the following output:

      Output

      namespace/openfaas created namespace/openfaas-fn created

      Next, you’ll need to add the OpenFaaS Helm repository, which hosts the OpenFaaS chart. To do this, run the following command:

      • helm repo add openfaas https://openfaas.github.io/faas-netes/

      Helm will display the following output:

      Output

      "openfaas" has been added to your repositories

      Refresh Helm’s chart cache:

      You’ll see the following output:

      Output

      Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "openfaas" chart repository ...Successfully got an update from the "jetstack" chart repository ...Successfully got an update from the "stable" chart repository Update Complete. ⎈ Happy Helming!⎈

      Before installing OpenFaaS, you’ll need to customize some chart parameters. You’ll store them on your local machine, in a file named values.yaml. Create and open the file with your text editor:

      Add the following lines:

      values.yaml

      functionNamespace: openfaas-fn
      generateBasicAuth: true
      
      ingress:
        enabled: true
        annotations:
          kubernetes.io/ingress.class: "nginx"
        hosts:
          - host: openfaas.your_domain
            serviceName: gateway
            servicePort: 8080
            path: /
      

      First, you specify the namespace where functions will be stored by assigning openfaas-fn to the functionNamespace variable. By setting generateBasicAuth to true, you order Helm to set up mandatory authentication when accessing the OpenFaaS web UI and to generate an admin username and password login combination for you.

      Then, you enable Ingress creation and further configure it to use the Nginx Ingress Controller and serve the gateway OpenFaaS service at your domain.

      Remember to replace openfaas.your_domain with your desired domain from the prerequisites. When you are done, save and close the file.

      Finally, install OpenFaaS into the openfaas namespace with the customized values:

      • helm upgrade openfaas --install openfaas/openfaas --namespace openfaas -f values.yaml

      You will see the following output:

      Output

      Release "openfaas" does not exist. Installing it now. NAME: openfaas LAST DEPLOYED: ... NAMESPACE: openfaas STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: To verify that openfaas has started, run: kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas" To retrieve the admin password, run: echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode)

      The output shows that the installation was successful. Run the following command to reveal the password for the admin account:

      • echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode) | tee openfaas-password.txt

      The decoded password is written to the output and to a file called openfaas-password.txt at the same time using tee. Note the output, which is your OpenFaaS password for the admin account.

      You can watch OpenFaaS containers become available by running the following command:

      • kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas"

      When all listed deployments become ready, type CTRL + C to exit.

      You can now navigate to the specified domain in your web browser. Input admin as the username and the accompanying password when prompted. You’ll see the OpenFaaS web UI:

      OpenFaaS - Empty Control Panel

      You’ve successfully installed OpenFaaS and exposed its control panel at your domain. Next, you’ll secure it using free TLS certificates from Let’s Encrypt.

      Step 2 — Enabling TLS for Your Domain

      In this step, you’ll secure your exposed domain using Let’s Encrypt certificates, provided by cert-manager.

      To do this, you’ll need to edit the ingress config in values.yaml. Open it for editing:

      Add the highlighted lines:

      values.yaml

      generateBasicAuth: true
      
      ingress:
        enabled: true
        annotations:
          kubernetes.io/ingress.class: "nginx"
          cert-manager.io/cluster-issuer: letsencrypt-prod
        tls:
          - hosts:
              - openfaas.your_domain
            secretName: openfaas-crt
        hosts:
          - host: openfaas.your_domain
            serviceName: gateway
            servicePort: 8080
            path: /
      

      The tls block defines in what Secret the certificates for your sites (listed under hosts) will store their certificates, which the letsencrypt-prod ClusterIssuer issues. Generally, the specified Secret must be different for every Ingress in your cluster.

      Remember to replace openfaas.your_domain with your desired domain, then save and close the file.

      Apply the changes to your cluster by running the following command:

      • helm upgrade openfaas --install openfaas/openfaas --namespace openfaas -f values.yaml

      You’ll see the following output:

      Output

      Release "openfaas" has been upgraded. Happy Helming! NAME: openfaas LAST DEPLOYED: ... NAMESPACE: openfaas STATUS: deployed REVISION: 2 TEST SUITE: None NOTES: To verify that openfaas has started, run: kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas" To retrieve the admin password, run: echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode)

      You’ll need to wait a few minutes for the Let’s Encrypt servers to issue a certificate for your domain. In the meantime, you can track its progress by inspecting the output of the following command:

      • kubectl describe certificate openfaas-crt -n openfaas

      The end of the output will look similar to this:

      Output

      Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal GeneratedKey 24m cert-manager Generated a new private key Normal Requested 16m cert-manager Created new CertificateRequest resource "openfaas-crt-1017759607" Normal Issued 16m cert-manager Certificate issued successfully

      When the last line of output reads Certificate issued successfully, you can exit by pressing CTRL + C. Refresh your domain in your browser to test. You’ll see the padlock to the left of the address bar in your browser, signifying that your connection is secure.

      You’ve secured your OpenFaaS domain using free TLS certificates from Let’s Encrypt. Now you’ll use the web UI and manage functions from it.

      Step 3 — Deploying Functions via the Web UI

      In this section, you’ll explore the OpenFaaS web UI and then deploy, manage, and invoke functions from it.

      The OpenFaaS web UI has two main parts: on the left-hand side, a column where the deployed functions will be listed, and the central panel, where you’ll see detailed info about a selected function and be able to interact with it.

      To deploy a new function, click the Deploy New Function button underneath the OpenFaaS logo on the upper left. You’ll see a dialog asking you to choose a function:

      OpenFaaS - Deploy a New Function dialog

      The FROM STORE tab lists pre-made functions from the official OpenFaaS function store that you can deploy right away. Each function is shown with a short description, and you can select the link icon on the right of a function to take a look at its source code. To deploy a store function from this list, select it, and then click the DEPLOY button.

      You can also supply your own function by switching to the CUSTOM tab:

      OpenFaaS - Deploy a Custom Function

      Here, you’d need to specify a Docker image of your function that is configured specifically for OpenFaaS and available at a Docker registry (such as Docker Hub). In this step, you’ll deploy a pre-made function from the OpenFaaS store, then in the next steps you’ll create and deploy custom functions to Docker Hub.

      You’ll deploy the NodeInfo function, which returns information about the machine it’s deployed on, such as CPU architecture, number of cores, total RAM memory available, and uptime (in seconds).

      From the list of store functions, select NodeInfo and click DEPLOY. It will soon show up in the list of deployed functions.

      OpenFaaS - NodeInfo Deployed

      Select it. In the central part of the screen, you’ll see basic information about the deployed function.

      OpenFaaS - Deployed Function Info

      The status of the function updates in real time, and should quickly turn to Ready. If it stays at Not Ready for longer periods of time, it’s most likely that your cluster lacks the resources to accept a new pod. You can follow How To Resize Droplets for information on how to fix this.

      Once Ready, the deployed function is accessible at the shown URL. To test it, you can navigate to the URL in your browser, or call it from the Invoke function panel located beneath the function info.

      OpenFaaS - Invoke Deployed Function

      You can select between Text, JSON, and Download to indicate the type of response you expect. If you want the request to be a POST instead of GET, you can supply request data in the Request body field.

      To call the nodeinfo function, click the INVOKE button. OpenFaaS will craft and execute a HTTP request according to the selected options and fill in the response fields with received data.

      OpenFaaS - nodeinfo Function Response

      The response status is HTTP 200 OK, which means that the request was executed successfully. The response body contains system information that the NodeInfo function collects, meaning that it’s properly accessible and working correctly.

      To delete a function, select it from the list and click the garbage can icon in the right upper corner of the page. When prompted, click OK to confirm. The function’s status will turn to Not Ready (which means it’s being removed from the cluster) and the function will soon vanish from the UI altogether.

      In this step, you’ve used the OpenFaaS web UI, as well as deploy and manage functions from it. You’ll now see how you can deploy and manage OpenFaaS functions using the command line.

      Step 4 — Managing Functions Using the faas-cli

      In this section, you’ll configure the faas-cli to work with your cluster. Then, you’ll deploy and manage your existing functions through the command line.

      To avoid having to specify your OpenFaaS domain every time you run the faas-cli, you’ll store it in an environment variable called OPENFAAS_URL, whose value the faas-cli will automatically pick up and use during execution.

      Open .bash_profile in your home directory for editing:

      Add the following line:

      ~/.bash_profile

      . . .
      export OPENFAAS_URL=https://openfaas.your_domain
      

      Remember to replace openfaas.your_domain with your domain, then save and close the file.

      To avoid having to log in again, manually evaluate the file:

      Now, ensure that you have faas-cli installed on your local machine. If you haven’t yet installed it, do so by following the instructions outlined in the official docs.

      Then, set up your login credentials by running the following command:

      • cat ~/openfaas-password.txt | faas-cli login --username admin --password-stdin

      The output will look like:

      Output

      Calling the OpenFaaS server to validate the credentials... credentials saved for admin https://openfaas.your_domain

      To deploy a function from the store, run the following command:

      • faas store deploy function_name

      You can try deploying nodeinfo by running:

      • faas store deploy nodeinfo

      You’ll see output like the following:

      Output

      Deployed. 202 Accepted. URL: https://openfaas.your_domain/function/nodeinfo

      To list deployed functions, run faas list:

      Your existing functions will be shown:

      Output

      Function Invocations Replicas nodeinfo 0 1

      To get detailed info about a deployed function, use faas describe:

      The output will be similar to:

      Name:                nodeinfo
      Status:              Ready
      Replicas:            1
      Available replicas:  1
      Invocations:         0
      Image:               functions/nodeinfo-http:latest
      Function process:
      URL:                 https://openfaas.your_domain/function/nodeinfo
      Async URL:           https://openfaas.your_domain/async-function/nodeinfo
      Labels:              faas_function : nodeinfo
                           uid : 514253614
      Annotations:         prometheus.io.scrape : false
      

      You can invoke a function with faas invoke:

      You’ll get the following message:

      Output

      Reading from STDIN - hit (Control + D) to stop.

      You can then provide a request body. If you do, the method will be POST instead of GET. When you are done with inputting data, or want the request to be GET, press CTRL + D. The faas-cli will then execute the inferred request and output the response, similarly to the web UI.

      To delete a function, run faas remove:

      You’ll get the following output:

      Output

      Deleting: nodeinfo. Removing old function.

      Run faas list again to see that nodeinfo was removed:

      Output

      Function Invocations Replicas

      In this step, you’ve deployed, listed, invoked, and removed functions in your cluster from the command line using the faas-cli. In the next step, you’ll create your own function and deploy it to your cluster.

      Step 5 — Creating and Deploying a New Function

      Now you’ll create a sample Node.JS function using the faas-cli and deploy it to your cluster.

      The resulting function you’ll create will be packaged as a Docker container and published on Docker Hub. To be able to publish containers, you’ll need to log in by running the following command:

      Enter your Docker Hub username and password when prompted to finish the login process.

      You’ll store the sample Node.JS function in a folder named sample-js-function. Create it using the following command:

      Navigate to it:

      Populate the directory with the template of a JS function by running the following command:

      • faas new sample-js --lang node

      The output will look like this:

      Output

      2020/03/24 17:06:08 No templates found in current directory. 2020/03/24 17:06:08 Attempting to expand templates from https://github.com/openfaas/templates.git 2020/03/24 17:06:10 Fetched 19 template(s) : [csharp csharp-armhf dockerfile go go-armhf java11 java11-vert -x java8 node node-arm64 node-armhf node12 php7 python python-armhf python3 python3-armhf python3-debian ru by] from https://github.com/openfaas/templates.git Folder: sample-js created. ___ _____ ____ / _ _ __ ___ _ __ | ___|_ _ __ _/ ___| | | | | '_ / _ '_ | |_ / _` |/ _` ___ | |_| | |_) | __/ | | | _| (_| | (_| |___) | ___/| .__/ ___|_| |_|_| __,_|__,_|____/ |_| Function created in folder: sample-js Stack file written: sample-js.yml ...

      As written in the output, the code for the function itself is in the folder sample-js, while the OpenFaaS configuration for the function is in the file sample-js.yaml. Under the sample-js directory (which resembles a regular Node.JS project) are two files, handler.js and package.json.

      handler.js contains actual JS code that will return a response when the function is called. The contents of the handler look like the following:

      sample-js-function/sample-js/handler.js

      "use strict"
      
      module.exports = async (context, callback) => {
          return {status: "done"}
      }
      

      It exports a lambda function with two parameters, a context with request data and a callback that you can use to pass back response data, instead of just returning it.

      Open this file for editing:

      • nano sample-js/handler.js

      Change the highlighted line as follows:

      sample-js-function/sample-js/handler.js

      "use strict"
      
      module.exports = async (context, callback) => {
          return {status: "<h1>Hello Sammy!</h1>"}
      }
      

      When you are done, save and close the file. This OpenFaaS function will, when called, write Hello Sammy! to the response.

      Next, open the configuration file for editing:

      It will look like the following:

      sample-js-function/sample-js.yml

      version: 1.0
      provider:
        name: openfaas
        gateway: https://openfaas.your_domain
      functions:
        sample-js:
          lang: node
          handler: ./sample-js
          image: sample-js:latest
      

      For the provider, it specifies openfaas and a default gateway. Then, it defines the sample-js function, specifies its language (node), its handler and the Docker image name, which you’ll need to modify to include your Docker Hub account username, like so:

      sample-js-function/sample-js.yml

      version: 1.0
      provider:
        name: openfaas
        gateway: http://127.0.0.1:8080
      functions:
        sample-js:
          lang: node
          handler: ./sample-js
          image: your_docker_hub_username/sample-js:latest
      

      Save and close the file.

      Then, build the Docker image, push it to Docker Hub, and deploy it on your cluster, all at the same time by running the following command:

      There will be a lot of output (mainly from Docker), which will end like this:

      Output

      . . . [0] < Pushing sample-js [your_docker_hub_username/sample-js:latest] done. [0] Worker done. Deploying: sample-js. Deployed. 202 Accepted. URL: https://openfaas.your_domain/function/sample-js

      Invoke your newly deployed function to make sure it’s working:

      Press CTRL + D. You’ll see the following output:

      Output

      <h1>Hello Sammy!</h1>

      This means that the function was packaged and deployed correctly.

      You can remove the function by running:

      You have now successfully created and deployed a custom Node.JS function on your OpenFaaS instance in your cluster.

      Conclusion

      You’ve deployed OpenFaaS on your DigitalOcean Kubernetes cluster and are ready to deploy and access both pre-made and custom functions. Now, you are able to implement the Function as a Service architecture, which can increase resource utilization and bring performance improvements to your apps.

      If you’d like to learn more about advanced OpenFaaS features, such as autoscaling for your deployed functions and monitoring their performance, visit the official docs.



      Source link

      Learning Go Functions, Loops, and Errors – A Tutorial


      Updated by Linode Contributed by Mihalis Tsoukalos

      Introduction

      Go is a modern, open source, and general-purpose programming language that began as an internal Google project and was officially announced at the end of 2009. Go was inspired by many other programming languages including C, Pascal, Alef, and Oberon. Its spiritual fathers were Robert Griesemer, Ken Thomson, and Rob Pike, who all designed Go as a language for professional programmers that want to build reliable, robust, and efficient software. Apart from its syntax and its standard functions, Go comes with a rich standard library.

      In this Guide

      This guide will cover the following topics:

      Note

      This guide was written with Go version 1.13.

      Before You Begin

      1. You will need Go installed on your computer. To get it, go to Go’s official download page and get the installer for your operating system, or you can install it from source. Follow the installation instructions for your operating system.

      2. Add /usr/local/go/bin to the PATH environment variable:

        export PATH=$PATH:/usr/local/go/bin
        

        You may need to restart your shell for this change to apply.

      The Advantages of Go

      Although Go is not perfect, it has many advantages, including the following:

      • It is a modern programming language that was made by experienced developers for developers.
      • The code is easy to read.
      • Go keeps concepts orthogonal, or simple, because a few orthogonal features work better than many overlapping ones.
      • The compiler prints practical warnings and error messages that help you solve the actual problem.
      • It has support for procedural, concurrent, and distributed programming.
      • Go supports garbage collection so you do not have to deal with memory allocation and deallocation.
      • Go can be used to build web applications and it provides a simple web server for testing purposes.
      • The standard Go library offers many packages that simplify the work of the developer.
      • It uses static linking by default, which means that the produced binary files can be easily transferred to other machines with the same OS and architecture. As a consequence, once a Go program is compiled successfully and the executable file is generated, the developer does not need to worry about dependencies and library versions.
      • The code is portable, especially among UNIX machines.
      • Go can be used for writing UNIX systems software.
      • It supports Unicode by default which means that you do not need any extra code for printing characters from multiple human languages or symbols.

      Executing Go code

      There are two kinds of Go programs: autonomous programs that are executable, and Go libraries. Go does not care about an autonomous program’s file name. What matters is that the package name is main and that there is a single main() function in it. This is because the main() function is where program execution begins. As a result, you cannot have multiple main() functions in the files of a single project.

      A Simple Go program

      This is the Go version of the Hello World program:

      ./helloworld.go
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      package main
      
      import (
          "fmt"
      )
      
      func main() {
          fmt.Println("Hello World!")
      }
      • All Go code is delivered within Go packages. For executable programs, the package name should be main. Package declarations begin with the package keyword.

      • Executable programs should have a function named main() without any function parameters. Function definitions begin with the func keyword.

      • Go packages might include import statements for importing Go packages. However, Go demands that you use some functionality from each one of the packages that you import. There is a way to bypass this rule, however, it is considered a bad practice to do this.

        The helloworld.go file above imports the fmt package and uses the fmt.Println() function from that package.

        Note

        All exported package functions begin with an uppercase letter. This follows the Go rule: if you export something outside the current package, it should begin with an uppercase letter. This rule applies even if the field of the Go structure or the global variable is included in a Go package.

      • Go statements do not need to end with a semicolon. However, you are free to use semicolons if you wish. For more information on formatting with curly braces, see the section below.

      1. Now that you better understand the helloworld.go program, execute it with the go run command:

        go run helloworld.go
        

        You will see the following output:

          
        Hello World!
        
        

        This is the simplest of two ways that you can execute Go code. The go run command compiles the code and creates a temporary executable file that is automatically executed and then it deletes that temporary executable file. This is similar to using a scripting programming language.

      2. The second method to execute Go code is to use the build command. Run the following command to use this method:

        go build helloworld.go
        

        The result of that command is a binary executable file that you have to manually execute. This method is similar to the way you execute C code on a UNIX machine. The executable file is named after the Go source filename, which means that in this case the result will be an executable file named helloworld. Go creates statically linked executable files that have no dependencies to external libraries.

      3. Execute the helloworld file:

        ./helloworld
        

        You will see the following output:

          
        Hello World!
        
        

        Note

        The go run command is usually used while experimenting and developing new Go projects. However, if you need to transfer an executable file to another system with the same architecture, you should use go build.

      Formatting Curly Braces

      The following version of the “Hello World” program will not compile:

      ./curly.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      package main
      
      import (
          "fmt"
      )
      
      func main()
      {
          fmt.Println("Hello World!")
      }
      1. Execute the program above, and observer the error message generated by the compiler:

        go run curly.go
        
          
        # command-line-arguments
        ./curly.go:7:6: missing function body
        ./curly.go:8:1: syntax error: unexpected semicolon or newline before {
        
        
      • This error message is generated because Go requires the use of semicolons as statement terminators in many contexts and the compiler automatically inserts the required semicolons when it thinks that they are necessary. Putting the opening curly brace ({) on its own line makes the Go compiler look for a semicolon at the end of the previous line (func main()), which is the cause of the error message.

      • There is only one way to format curly braces in Go; the opening curly brace must not appear on it’s own line. Additionally, you must use curly braces even if a code block contains a single Go statement, like in the body of a for loop. You can see an example of this in the first version of the helloworld.go program or in the Loops in Go section.

      The Assignment Operator and Short Variable Declarations

      • Go supports assignment (=) operators and short variable declarations (:=).
      • With := you can declare a variable and assign a value to it at the same time. The type of the variable is inferred from the given value.
      • You can use = in two cases. First, to assign a new value to an existing variable and second, to declare a new variable, provided that you also give its type.

        For example, var aVariable int = 10, is equivalent to aVariable := 10 assuming aVariable is an int.

      • When you specifically want to control a variable’s type, it is safer to declare the variable and its type using var and then assign a value to it using =.

      Loops in Go

      The file loops.go demonstrates loops in Go:

      ./loops.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
      
      package main
      
      import (
          "fmt"
      )
      
      func main() {
          for loopIndex := 0; loopIndex < 20; loopIndex++ {
              if loopIndex%10 == 0 {
                  continue
              }
      
              if loopIndex == 19 {
                  break
              }
              fmt.Print(loopIndex, " ")
          }
          fmt.Println()
      
          // Use break to exit the for loop
          loopIndex := 10
          for {
              if loopIndex < 0 {
                  break
              }
              fmt.Print(loopIndex, " ")
              loopIndex--
          }
          fmt.Println()
      
          // This is similar to a while(true) do something loop
          loopIndex = 0
          anExpression := true
          for ok := true; ok; ok = anExpression {
              if loopIndex > 10 {
                  anExpression = false
              }
      
              fmt.Print(loopIndex, " ")
              loopIndex++
          }
          fmt.Println()
      
          anArray := [5]int{0, 1, -1, 2, -2}
          for loopIndex, value := range anArray {
              fmt.Println("index:", loopIndex, "value: ", value)
          }
      }
      • There are two types of for loops in Go. Traditional for loops that use a control variable initialization, condition, and afterthought; and those that iterate over the elements of a Go data type such as an array or a map using the range keyword.

      • Go has no direct support for while loops. If you want to use a while loop, you can emulate it with a for loop.

      • In their simplest form, for loops allow you to iterate, a predefined number of times, for as long as a condition is valid, or according to a value that is calculated at the beginning of the for loop. Such values include the size of a slice or an array, or the number of keys on a map. However, range is more often used for accessing all the elements of a slice, an array, or a map because you do not need to know the object’s cardinality in order to process its elements one by one. For simplicity, this example uses an array, and a later example will use a slice.

      • You can completely exit a for loop using the break keyword. The break keyword also allows you to create a for loop without an exit condition because the exit condition can be included in the code block of the for loop. You are also allowed to have multiple exit conditions in a for loop.

      • You can skip a single iteration of a for loop using the continue keyword.

      1. Execute the loops.go program:

        go run loops.go
        

        You will see the following output:

          
        1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17 18
        10 9 8 7 6 5 4 3 2 1 0
        0 1 2 3 4 5 6 7 8 9 10 11
        index: 0 value:  0
        index: 1 value:  1
        index: 2 value:  -1
        index: 3 value:  2
        index: 4 value:  -2
            
        

      Functions in Go

      Functions are first class citizens in Go, which means that functions can be parameters to other functions as well as returned by functions. This section will illustrate various types of functions.

      Go also supports anonymous functions. These can be defined inline without the need for a name and they are usually used for implementing operations that require a small amount of code. In Go, a function can return an anonymous function or take an anonymous function as one of its arguments. Additionally, anonymous functions can be attached to Go variables. In functional programming terminology anonymous functions are called closures. It is considered a good practice for anonymous functions to have a small implementation and a local focus.

      Regular functions

      This section will present the implementation of some traditional functions.

      ./functions.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
      
      package main
      
      import (
          "fmt"
      )
      
      func doubleSquare(firstNum int) (int, int) {
          return firstNum * 2, firstNum * firstNum
      }
      
      func namedMinMax(firstNum, secondNum int) (min, max int) {
          if firstNum > secondNum {
              min = secondNum
              max = firstNum
          } else {
              min = firstNum
              max = secondNum
          }
          return
      }
      
      func minMax(firstNum, secondNum int) (min, max int) {
          if firstNum > secondNum {
              min = secondNum
              max = firstNum
          } else {
              min = firstNum
              max = secondNum
          }
          return min, max
      }
      
      func main() {
          secondNum := 10
      
          square := func(numberToSquare int) int {
              return numberToSquare * numberToSquare
          }
          fmt.Println("The square of", secondNum, "is", square(secondNum))
      
          double := func(numberToDouble int) int {
              return numberToDouble + numberToDouble
          }
          fmt.Println("The double of", secondNum, "is", double(secondNum))
      
          fmt.Println(doubleSquare(secondNum))
          doubledNumber, squaredNumber := doubleSquare(secondNum)
          fmt.Println(doubledNumber, squaredNumber)
      
          value1 := -10
          value2 := -1
          fmt.Println(minMax(value1, value2))
          min, max := minMax(value1, value2)
          fmt.Println(min, max)
          fmt.Println(namedMinMax(value1, value2))
          min, max = namedMinMax(value1, value2)
          fmt.Println(min, max)
      }
      • The main() function takes no arguments and returns no arguments. Once the special function main() exits, the program automatically ends.

      • The doubleSquare() function requires a single int parameter and returns two int values, which is defined as (int, int).

      • All function arguments must have a name – variadic functions are the only exception to this rule.

      • If a function returns a single value, you do not need to put parenthesis around its type.

      • Because namedMinMax() has named return values in its signature, the min and max parameters are automatically returned in the order in which they were put in the function definition. Therefore, the function does not need to explicitly return any variables or values in its return statement at the end, and does not. minMax() function has the same functionality as namedMinMax() but it explicitly returns its values demonstrating that both ways are valid.

      • Both square and double variables in main() are assigned an anonymous function. However, nothing stops you from changing the value of square, double, or any other variable that holds the result of an anonymous function, afterwards. This means that both variables may have a different value in the future.

      1. Execute the functions.go program.

        go run functions.go
        

        Your output will resemble the following:

          
        The square of 10 is 100
        The double of 10 is 20
        20 100
        20 100
        -10 -1
        -10 -1
        -10 -1
        -10 -1
            
        

      Variadic functions

      Variadic functions are functions that accept a variable number of arguments. The most popular variadic functions in Go can be found in the fmt package. The code of variadic.go illustrates the creation and the use of variadic functions.

      ./variadic.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
      
      package main
      
      import (
          "fmt"
      )
      
      func varFunc(input ...string) {
          fmt.Println(input)
      }
      
      func oneByOne(message string, sliceOfNumbers ...int) int {
          fmt.Println(message)
          sum := 0
          for indexInSlice, sliceElement := range sliceOfNumbers {
              fmt.Print(indexInSlice, sliceElement, "t")
              sum = sum + sliceElement
          }
          fmt.Println()
          sliceOfNumbers[0] = -1000
          return sum
      }
      
      func main() {
          many := []string{"12", "3", "b"}
          varFunc(many...)
          sum := oneByOne("Adding numbers...", 1, 2, 3, 4, 5, -1, 10)
          fmt.Println("Sum:", sum)
          sliceOfNumbers := []int{1, 2, 3}
          sum = oneByOne("Adding numbers...", sliceOfNumbers...)
          fmt.Println(sliceOfNumbers)
      }
      • The ... operator used as a prefix to a type like ...int is called the pack operator, whereas the unpack operator appends a slice like sliceOfNumbers.... A slice is a Go data type that is essentially an abstraction of an array of unspecified length.

      • Each variadic function can use the pack operator once. The oneByOne() function accepts a single string and a variable number of integer arguments using the sliceOfNumbers slice.

      • The varFunc function accepts a single argument and just calls the fmt.Println() function.

      • Another note about slices: the second call to oneByOne() is using a slice. Any changes you make to that slice inside the variadic function will persist after the function exits because this is how slices work in Go.

      1. Execute the variadic.go program:

        go run variadic.go
        

        The output will resemble the following

          
        [12 3 b]
        Adding numbers...
        0 1     1 2     2 3     3 4     4 5     5 -1     6 10
        Sum: 24
        Adding numbers...
        0 1     1 2     2 3
        [-1000 2 3]
            
        

      Functions and pointer variables

      Go supports pointers and this section will briefly present how functions can work with pointers. A future Go guide will talk about pointers in more detail, but here is a brief overview.

      ./fPointers.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
      
      package main
      
      import (
          "fmt"
      )
      
      func getPointer(varToPointer *float64) float64 {
          return *varToPointer * *varToPointer
      }
      
      func returnPointer(testValue int) *int {
          squareTheTestValue := testValue * testValue
          return &squareTheTestValue
      }
      
      func main() {
          testValue := -12.12
          fmt.Println(getPointer(&testValue))
          testValue = -12
          fmt.Println(getPointer(&testValue))
      
          theSquare := returnPointer(10)
          fmt.Println("sq value:", *theSquare)
          fmt.Println("sq memory address:", theSquare)
      }
      • The getPointer() function takes a pointer argument to a float64, which is defined as varToPointer *float64, where returnPointer() returns a pointer to an int, which is declared as *int.
      1. Execute the fPointers.go program:

        go run fPointers.go
        

        The output will resemble the following:

          
        146.8944
        144
        sq value: 100
        sq memory address: 0xc00001a0b8
            
        

      Functions with Functions as Parameters

      Go functions can have functions as parameters.

      ./fArgF.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      package main
      
      import "fmt"
      
      func doubleIt(numToDouble int) int {
          return numToDouble + numToDouble
      }
      
      func squareIt(numToSquare int) int {
          return numToSquare * numToSquare
      }
      
      func funFun(functionName func(int) int, variableName int) int {
          return functionName(variableName)
      }
      
      func main() {
          fmt.Println("funFun Double:", funFun(doubleIt, 12))
          fmt.Println("funFun Square:", funFun(squareIt, 12))
          fmt.Println("Inline", funFun(func(numToCube int) int { return numToCube * numToCube * numToCube }, 12))
      }
      • The funFun() function accepts two parameters, a function parameter named functionName and an int value. The functionName parameter should be a function that takes one int argument and returns an int value.

      • The first fmt.Println() call in main() uses funFun() and passes the doubleIt function, without any parentheses, as its first parameter.

      • The second fmt.Println() call uses funFun() with squareIt as its first parameter.

      • In the last fmt.Println() statement the implementation of the function parameter is defined inside the call to funFun() using an anonymous function.

      1. Execute the fArgF.go program:

        go run fArgF.go
        

        The output will resemble the following:

          
        function1: 24
        function2: 144
        Inline 1728
            
        

      Functions Returning Functions

      Go functions can return functions.

      ./fRetF.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      package main
      
      import (
          "fmt"
      )
      
      func squareFunction() func() int {
          numToSquare := 0
          return func() int {
              numToSquare++
              return numToSquare * numToSquare
          }
      }
      
      func main() {
          square1 := squareFunction()
          square2 := squareFunction()
      
          fmt.Println("First Call to square1:", square1())
          fmt.Println("Second Call to square1:", square1())
          fmt.Println("First Call to square2:", square2())
          fmt.Println("Third Call to square1:", square1())
      }
      • squareFunction() returns an anonymous function with the func() int signature.

      • As squareFunction() is called two times, you will need to use two separate variables, square1 and square2 to keep the two return values.

      1. Execute the fRetF.go program:

        go run fRetF.go
        

        Your output will resemble the following:

          
        First Call to square1: 1
        Second Call to square1: 4
        First Call to square2: 1
        Third Call to square1: 9
            
        

        Notice that the values of square1 and square2 are not connected even though they both came from squareFunction().

      Errors in Go

      Errors and error handling are two important topics in Go. Go puts so much importance on error messages that it has a dedicated data type for errors, aptly named error. This also means that you can easily create your own error messages if you find that what Go gives you is not adequate. You will most likely need to create and handle your own errors when you are developing your own Go packages.

      Recognizing an error condition is one task, while deciding how to react to an error condition is another task. Therefore, some error conditions might require that you immediately stop the execution of the program, whereas in other error situations, you might just print a warning message and continue.

      ./errors.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
      
      package main
      
      import (
          "errors"
          "fmt"
          "strconv"
      )
      
      func main() {
      
          customError := errors.New("My Custom Error!")
          if customError.Error() == "My Custom Error!" {
              fmt.Println("!!")
          }
      
          stringToConvert1 := "123"
          stringToConvert2 := "43W"
          _, err := strconv.Atoi(stringToConvert1)
          if err != nil {
              fmt.Println(err)
              return
          }
      
          _, err = strconv.Atoi(stringToConvert2)
          if err != nil {
              fmt.Println(err)
              return
          }
      }
      • The strconv.Atoi() function tries to convert a string into an integer, provided that the string is a valid integer, and returns two things, an integer value and an error variable. If the error variable is nil, then the conversion was successful and you get a valid integer. The _ character tells Go to ignore one, as in this case, or more of the return values of a function.

      • Most of the time, you need to check whether an error variable is equal to nil and then act accordingly. This kind of Go code is very popular in Go programs and you will see it and use it multiple times.

      • Also presented here is the errors.New() function that allows you to create a custom error message and errors.Error() function that allows you to convert an error variable into a string variable.

      1. Execute the errors.go program:

        go run errors.go
        

        Your output will resemble the following:

          
        !!
        strconv.Atoi: parsing "43W": invalid syntax
            
        

      Summary

      In this guide you learned the basics about the Go programming language, how to execute programs, how to write loops, how to handle errors, and you saw examples for various function types.

      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

      How To Use Variadic Functions in Go


      Introduction

      A variadic function is a function that accepts zero, one, or more values as a single argument. While variadic functions are not the common case, they can be used to make your code cleaner and more readable.

      Variadic functions are more common than they seem. The most common one is the Println function from the fmt package.

      func Println(a ...interface{}) (n int, err error)
      

      A function with a parameter that is preceded with a set of ellipses (...) is considered a variadic function. The ellipsis means that the parameter provided can be zero, one, or more values. For the fmt.Println package, it is stating that the parameter a is variadic.

      Let’s create a program that uses the fmt.Println function and pass in zero, one, or more values:

      print.go

      package main
      
      import "fmt"
      
      func main() {
          fmt.Println()
          fmt.Println("one")
          fmt.Println("one", "two")
          fmt.Println("one", "two", "three")
      }
      

      The first time we call fmt.Println, we don’t pass any arguments. The second time we call fmt.Println we pass in only a single argument, with the value of one. Then we pass one and two, and finally one, two, and three.

      Let’s run the program with the following command:

      We’ll see the following output:

      Output

      one one two one two three

      The first line of the output is blank. This is because we didn’t pass any arguments the first time fmt.Println was called. The second time the value of one was printed. Then one and two, and finally one, two, and three.

      Now that we have seen how to call a variadic function, let’s look at how we can define our own variadic function.

      Defining a Variadic Function

      We can define a variadic function by using an ellipsis (...) in front of the argument. Let’s create a program that greets people when their names are sent to the function:

      hello.go

      package main
      
      import "fmt"
      
      func main() {
          sayHello()
          sayHello("Sammy")
          sayHello("Sammy", "Jessica", "Drew", "Jamie")
      }
      
      func sayHello(names ...string) {
          for _, n := range names {
              fmt.Printf("Hello %sn", n)
          }
      }
      

      We created a sayHello function that takes only a single parameter called names. The parameter is variadic, as we put an ellipsis (...) before the data type: ...string. This tells Go that the function can accept zero, one, or many arguments.

      The sayHello function receives the names parameter as a slice. Since the data type is a string, the names parameter can be treated just like a slice of strings ([]string) inside the function body. We can create a loop with the range operator and iterate through the slice of strings.

      If we run the program, we will get the following output:

      Output

      Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie

      Notice that nothing printed for the first time we called sayHello. This is because the variadic parameter was an empty slice of string. Since we are looping through the slice, there is nothing to iterate through, and fmt.Printf is never called.

      Let’s modify the program to detect that no values were sent in:

      hello.go

      package main
      
      import "fmt"
      
      func main() {
          sayHello()
          sayHello("Sammy")
          sayHello("Sammy", "Jessica", "Drew", "Jamie")
      }
      
      func sayHello(names ...string) {
          if len(names) == 0 {
              fmt.Println("nobody to greet")
              return
          }
          for _, n := range names {
              fmt.Printf("Hello %sn", n)
          }
      }
      

      Now, by using an if statement, if no values are passed, the length of names will be 0, and we will print out nobody to greet:

      Output

      nobody to greet Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie

      Using a variadic parameter can make your code more readable. Let’s create a function that joins words together with a specified delimiter. We’ll create this program without a variadic function first to show how it would read:

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"})
          fmt.Println(line)
      
          line = join(",", []string{"Sammy", "Jessica"})
          fmt.Println(line)
      
          line = join(",", []string{"Sammy"})
          fmt.Println(line)
      }
      
      func join(del string, values []string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      In this program, we are passing a comma (,) as the delimiter to the join function. Then we are passing a slice of values to join. Here is the output:

      Output

      Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

      Because the function takes a slice of string as the values parameter, we had to wrap all of our words in a slice when we called the join function. This can make the code difficult to read.

      Now, let’s write the same function, but we’ll use a variadic function:

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
          fmt.Println(line)
      
          line = join(",", "Sammy", "Jessica")
          fmt.Println(line)
      
          line = join(",", "Sammy")
          fmt.Println(line)
      }
      
      func join(del string, values ...string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      If we run the program, we can see that we get the same output as the previous program:

      Output

      Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

      While both versions of the join function do the exact same thing programmatically, the variadic version of the function is much easier to read when it is being called.

      Variadic Argument Order

      You can only have one variadic parameter in a function, and it must be the last parameter defined in the function. Defining parameters in a variadic function in any order other than the last parameter will result in a compilation error:

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
          fmt.Println(line)
      
          line = join(",", "Sammy", "Jessica")
          fmt.Println(line)
      
          line = join(",", "Sammy")
          fmt.Println(line)
      }
      
      func join(values ...string, del string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      This time we put the values parameter first in the join function. This will cause the following compilation error:

      Output

      ./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values

      When defining any variadic function, only the last parameter can be variadic.

      Exploding Arguments

      So far, we have seen that we can pass zero, one, or more values to a variadic function. However, there will be occasions when we have a slice of values and we want to send them to a variadic function.

      Let’s look at our join function from the last section to see what happens:

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          names := []string{"Sammy", "Jessica", "Drew", "Jamie"}
      
          line = join(",", names)
          fmt.Println(line)
      }
      
      func join(del string, values ...string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      If we run this program, we will receive a compilation error:

      Output

      ./join-error.go:10:14: cannot use names (type []string) as type string in argument to join

      Even though the variadic function will convert the parameter of values ...string to a slice of strings []string, we can’t pass a slice of strings as the argument. This is because the compiler expects discrete arguments of strings.

      To work around this, we can explode a slice by suffixing it with a set of ellipses (...) and turning it into discrete arguments that will be passed to a variadic function:

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          names := []string{"Sammy", "Jessica", "Drew", "Jamie"}
      
          line = join(",", names...)
          fmt.Println(line)
      }
      
      func join(del string, values ...string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      This time, when we called the join function, we exploded the names slice by appending ellipses (...).

      This allows the program to now run as expected:

      Output

      Sammy,Jessica,Drew,Jamie

      It’s important to note that we can still pass a zero, one, or more arguments, as well as a slice that we explode. Here is the code passing all the variations that we have seen so far:

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"}...)
          fmt.Println(line)
      
          line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
          fmt.Println(line)
      
          line = join(",", "Sammy", "Jessica")
          fmt.Println(line)
      
          line = join(",", "Sammy")
          fmt.Println(line)
      
      }
      
      func join(del string, values ...string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      Output

      Sammy,Jessica,Drew,Jamie Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

      We now know how to pass zero, one, or many arguments, as well as a slice that we explode, to a variadic function.

      Conclusion

      In this tutorial, we have seen how variadic functions can make your code cleaner. While you won’t always need to use them, you may find them useful:

      • If you find that you’re creating a temporary slice just to pass to a function.
      • When the number of input params are unknown or will vary when called.
      • To make your code more readable.

      To learn more about creating and calling functions, you can read How to Define and Call Functions in Go.



      Source link