Go Essentials: A Beginner's Guide to the world of Golang
Sep 29, 2023 · 33 minutes read
Hello everyone 👋, Today I have something interesting for you.
I was recently contributing to Porter - a CNCF sandbox project and Once I fixed the issue and was done with the pull request, I was motivated to make more contributions to the project. And, since the project was mainly written in Golang, I thought this is a good opportunity to learn Golang.
While learning Golang myself, I made some notes which are more than sufficient to get familiar with the syntax and necessary concepts of Golang. So, Let’s get started.
History of Golang #
- Golang was created by Engineers at Google motivated by their shared dislike for C++.
- Multicore processors were becoming universal and most programming languages like Java offered very little to get the most out of them.
- Go was created to keep useful features and combine the -
- easy and readable syntax of python
- efficiency of statically typed compiled languages like C,C++.
- support for modern, networked and multicore programming.
How is memory allocated in Golang? #
When Go starts a program, It requests a block of memory from operating system, cuts it into small chunks and manages it itself.
The basic unit of Go memory management is the mspan
, which consists of several
pages, each mspan
can allocate a specific size of object.
Go’s memory allocator allocates objects in three categories based on their size:
- Small objects(less than or equal to 16B)
- General objects (greater than 16B, less than or equal to 32KB)
- Large objects (greater than 32KB).
Data types in Golang #
Go has a concept of types that is either explicitly declared or implicitly inferred.
It is a fast, statically typed, compiled language that feels like dynamically typed, interpreted language.
- Number
- Integer - Integer types are based on the amount of memory they acquire,
uint
stands for unsigned integer andint
for signed integer.Data Type Memory uint8 8 bits or 1 byte uint16 16 bits or 2 bytes uint32 32 bits or 4 bytes uint64 64 bits or 8 bytes int8 8 bits or 1 byte int16 16 bits or 2 bytes int32 32 bits or 4 bytes int64 64 bits or 8 bytes int 4 bytes for 32-bit machines, 8 bytes for 64-bit machines - Float - Go supports
float32
andfloat64
data types, also referred to as single precision and double precision respectively.Data Type Memory float32 32 bits or 4 byte float64 64 bits or 8 bytes
- Integer - Integer types are based on the amount of memory they acquire,
- Rune - An item represented by a single value in single quotes which can take 1 to 4 bytes.
- String - It is a read-only slice of bytes and occupies 16 bytes of memory.
- Boolean - A boolean is either
true
orfalse
which represent 0 or 1 and occupies 1 byte of memory.
Variables #
Now, Let’s talk about how we can use variables inside Golang.
Variables are declared using var
keyword followed by variable_name
and its
type
such as int, string etc.
1// The syntax for declaring a variable is :
2// var <variable_name> <data_type>
3var str string
4
5// After declaring we can initialise variables like this
6
7str = "Hello World!"
Declaring + Initialising Variables #
- Standard way
1// The syntax for declaring and initialising a variable is :
2// var <variable_name> <data_type> = <value>
3
4var str string = "Hello"
5var num int = 10
6var b bool = true
7var decimal float64 = 69.420
- Shorthand way
1// Variables of same data type can be declared together like this
2var f,b string = "foo","bar"
3
4// Variables of different data type can also be declared together like this
5var (
6 str string = "foo"
7 num int = 10
8)
We can also use the walrus operator :=
to declare and initialise variables. We
do not explicitly mention type of variable, It is implicitly assigned a type by
the compiler.
1str := "foo"
Constants #
Constants are variables whose value is initialised once and can never be changed again. They are untyped unless explicitly given a type at the time of declaration, which allows flexibility.
Constants need to be initialised at the time of declaration as the
concept of zero value doesn’t apply to constants. And,
they can not be initialised using the :=
operator.
1// const <const_name> <data_type> = value
2
3const PI float64 = 3.14
4
5// const <const_name> = value
6
7const PI = 3.14
Printing Variables #
Variables can be printed to console by using methods provided by fmt
package such as Print
,Printf
,Println
etc.
Code to print a variable in go :
1package main
2import "fmt"
3
4func main() {
5 var hello string = "Hello"
6 var money float64 = 69.420
7
8 fmt.Print(str , " World")
9 fmt.Println(str , " World")
10 // Difference between Print and Println is that Print method doesn't add
11 // newline after printing a statement while Println does.
12
13 fmt.Printf("%s World! I have $ %f in my account.", hello, money);
14 // Printf stands for Print Formatter and it takes a template string with
15 // format specifiers and Object args(s).
16}
Printf Format specifiers #
Format specifiers are prefixed with a % symbol. There are different types of format specifiers for printing different types of variables.
Verb | Description |
---|---|
%v | default format |
%T | type of the value |
%d | integers |
%c | character |
%q | quoted characters/string |
%s | plain string |
%t | true or false |
%f | floating numbers |
%0.2f | floating numbers upto 2 decimal places |
Variable Scope #
Scopes are defined using Blocks which are declared using braces {
}
.
1{
2 //outer block
3 {
4 //inner block
5 }
6}
Inner block can access variables declared in Outer block but vice versa is not true. e.g:-
1func main() {
2 city := "Tokyo"
3 {
4 country := "Japan"
5 fmt.Println(country)
6 fmt.Println(city)
7 }
8 // fmt.Println(country) // this line will give error as Outer block cannot
9 // access variable `country` defined in inner block
10 fmt.Println(city)
11}
Local vs Global Variables #
- Local Variables
Local variables are declared inside a function or a block, also inside loops or conditional statements. These are only available inside and are not accessible outside the function or block.
1package main
2func main() {
3 name := "Zoro" // `name` is a local variable only available inside main func
4}
- Global Variables
Global variables are declared outside of a function/block and are available
throughout the lifetime of a program. These variables are declared at the top of
the program outside of any function or block and cannot be declared using the
walrus :=
operator, these have to be declared in the standard way.
1package main
2import "fmt"
3
4var name string = "Zoro"
5// `name` is a global variable available for use anywhere in the program
6
7func main() {
8 fmt.Println(name)
9}
Concept of Zero Value #
When you declare a variable and do not initialise it, It is given a default
value by the compiler known as zero value
which is different for different
data types.
e.g:-
Data Types | Zero values |
---|---|
bool | true |
int | 0 |
float64 | 0.0 |
string | "" |
etc. | … |
How to take user input? #
fmt
package provides a function Scanf
which is used to take input from user.
It takes a format string containing format specifiers, along with a variable number of arguments prefixed with ampersand
&
operator which means the address of the variable.Taking a single Input
1package main 2import "fmt" 3 4func main() { 5 var name string 6 fmt.Print("Enter your name: ") 7 fmt.Scanf("%s", &name) 8 fmt.Println("Hello ",name) 9}
Taking Multiple Inputs
1package main 2import "fmt" 3 4func main() { 5 var name string 6 var is_student bool 7 fmt.Print("Enter your name and Are you a student?: ") 8 fmt.Scanf("%s %t", &name, &is_student) 9 fmt.Println(name, is_student) 10}
Scanf returns two values
count
anderr
which stands for the number of arguments that the function writes to and any error thrown during the execution of scanf function.1package main 2import "fmt" 3 4func main() { 5 var str string 6 var num int 7 fmt.Print("Enter a string and a variable: ") 8 9 count, err := fmt.Scanf("%s %d", &str, &num) 10 11 fmt.Println("count: ",count) 12 fmt.Println("error: ",err) 13 fmt.Println(str, num) 14}
1❯ go run main.go 2Enter a string and a variable: hello world 3count: 1 4error: expected integer 5hello 0
Here, if we enter string two times then we will get an error. And the value of count is 1 as only the first input was handled successfully by Scanf which was
str
and It was assigned the value “hello” . But,num
variable couldn’t be assigned the given value as It was expecting an integer, So, It was assigned the zero value which is 0 for integers.
More on Types #
Checking Types #
We can check data types of variable using %T
or reflect.TypeOf
provided by
reflect package.
Using
%T
We can use
%T
format specifier to check the type of any variable1fmt.Printf("Variable Type of %v is %T", variable_name)
Using
reflect.TypeOf()
1package main 2import ( 3 "fmt" 4 "reflect" 5) 6func main() { 7 var sanji int = 3 8 var quote string = "My hands are only for cooking." 9 10 fmt.Printf("Variable sanji=%d is type of %v\n", sanji,reflect.TypeOf(sanji)) 11 fmt.Printf("Variable quote=%d is type of %v\n", quote,reflect.TypeOf(quote)) 12}
Type Casting/Conversion #
The process of converting one data type to another is called Type Casting or Conversion. However, It does not guarantee that the value will remain intact.
1package main
2import "fmt"
3
4func main() {
5 var f float64 = 69.420
6 var i int = int(f)
7 fmt.Println("Integer Value: ",i)
8}
1❯ go run main.go
2Integer Value: 69
Here the decimal value got truncated while converting float to integer.
Type Conversion can also be done by methods provided by a package strconv
Itoa()
- Converts integer to string and returns the value.1package main 2import ( 3 "fmt" 4 "strconv" 5) 6 7func main() { 8 var i int = 69 9 var s string = strconv.Itoa(i) // convert int to string 10 fmt.Println("String: ",s) 11}
It will print “69” as string.
Atoi()
- Convert string to integer and returns two values - integer, error1package main 2import ( 3 "fmt" 4 "strconv" 5) 6 7func main() { 8 var s string = "69" 9 i, err := strconv.Atoi(s) // convert string to int 10 fmt.Println("Integer: ",i); 11 fmt.Println("Error: ",err); 12}
Since the string contains “69”, It is correctly converted to string and the error is nil. If it contained any character such as a-z,A-z,symbols etc, Then It would have given an error.
Conditionals #
If-else and else if #
1if condition_1 { // parenthesis () around the condition is optional
2 // execute when condition_1 is true
3} else if condition_2 { // else should start from the same line where if block ends
4 // execute when condition_2 is true
5} else {
6 // if none of the condition is true
7}
Switch Case #
The expression doesn’t need to be in parenthesis and break; statement is also not needed.
1switch expression {
2 case value_1:
3 // execute when expression equals to value_1
4 case value_2, value_3:
5 // execute when expression equals to value_2 or value_3
6 default:
7 // execute when no matches found
8}
the fallthrough
keyword #
It is used to force the execution flow of switch case.
1switch expression {
2 case value_1:
3 // execute when expression equals to value_1
4 case value_2:
5 // execute when expression equals to value_2
6 fallthrough // It forces to execute the successive(next) case block
7 case value_3:
8 // execute when expression equals to value_3 or value_2 since fallthrough is
9 // used
10 default:
11 // execute when no matches found
12}
Switch with conditions #
We don’t give expression to switch statement and instead we can give conditions for each cases.
1switch {
2 case condition_1:
3 // execute when condition_1 is true
4 case condition_2:
5 // execute when condition_2 is true
6 default:
7 // execute when no conditions are true
8}
Looping in Go #
There is only one loop in Go that is for
loop.
1for initialisation; condition; post {
2 // statement(s)
3}
First initialisation happens, then if condition is true, statements inside loop are executed and after execution is completed, post statement (mostly increment of decrement) is executed.
Infinite Loop - If the condition of For Loop is omitted (not provided) then it becomes an infinite Loop.
Break and Continue
break
statement ends the loop immediately when it is encountered inside loop.continue
statement skips the current iteration and goes to the next one.
Arrays in Go #
An Array is a collection of similar data elements stored at contiguous memory locations.
In Golang, Arrays are of fixed length, And since, we have pointers in Go, we can get the address of any element of array.
1// Array Declaration
2// var <array_name> [<size_of_array>] <data_type>
3
4var num [5]int
5
6// Array Declaration and Initialisation
7// var <array_name>[<size_of_array>]<data_type> = [<size_of_array>]<data_type>{<values>}
8
9var num [5]int = [3]int{6,9,4,2,0}
10
11// Shorthand
12// <array_name> := [<size_of_array>]<data_type>{<values>}
13
14num := [3]int{6,9,4,2,0}
15
16// Shorthand using ellipsis(...)
17// <array_name> := [...]<data_type>{<values>}
18
19num := [...]int{6,9,4,2,0}
Length of Array #
To calculate length of an Array, we can use builtin len()
function.
1fruits := [4]string { "this", "is", "a", "test" }
2fmt.Println(len(fruits))
Looping through Array #
- Basic for loop
1arr := [3]int {6,9,4,2,0}
2
3for i := 0; i < len(arr[i]); i++ {
4 fmt.Println(arr[i]);
5}
- using range keyword
1arr := [3]int {6,9,4,2,0}
2
3for index, element := range arr {
4 fmt.Println(index, "->", element)
5}
6
7// If we don't need index, we can replace it with underscore `_`
8for _, element := range arr {
9 fmt.Println(element)
10}
MultiDimensional Array #
1// 2D Array
2arr := [3][2]int {{1, 2}, {3, 4}, {5, 6}}
3
4fmt.Println(arr[2][1]) // This will print 6
Slices #
A Slice is a continous segment of an underlying array, which offer more flexibility as they are variable typed (elements can be added or removed).
Slices have three major components
- pointer - It points to the first element of array accessible through the slice.
- length - It is the number of elements in slice, can be accessed using
len()
function. - capacity - It is the number of elements in the underlying array counting
from the first element of the slice, can be accessed using
cap()
function.
Creating a Slice #
1// <slice_name> := []<data_type>{<values>}
2grades := []int{10, 20, 30}
Creating slice from an array from start_index
till the end_index
(end_index
is not included).
1// array[start_index : end_index]
2arr := [8]int{2, 3, 4, 5, 6, 7, 8, 9}
3
4arr[1:5] // [3, 4, 5, 6]
5// array -> | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
6// ^
7// Slice Pointer
8// slice -> | 3 | 4 | 5 | 6 | -> length = 4, capacity = 7
If we omit the start_index
, it will be 0 by default and if we omit the
end_index
, it will be the length of array by default.
1// array[start_index : end_index]
2arr := [5]int{11, 12, 13, 14, 15}
3
4arr[ :3] // [11, 12, 13]
5arr[2: ] // [13, 14, 15]
6arr[ : ] // [11, 12, 13, 14, 15]
Using make
function
1// slice := make([]<data_type>, length, capacity)
2slice := make([]int, 5, 8)
Since Slices are references to the original array, If we mutate the slice it will aslo mutate the original array from which the slice was created.
1arr := [5]int{11, 12, 13, 14, 15}
2
3slice = arr[ :3] // [11, 12, 13]
4slice[1] = 1000
5
6// Now the slice will become, slice - [11, 1000, 13]
7// And, the original array, arr - [11, 1000, 13, 14, 15]
Appending to Slice #
append
function is used to append a slice or values of the same type as that
of the slice to the slice.
If the number of total values after appending become more than the capacity of the slice, then the slice’s capacity will be doubled.
Appending values to slice
1// slice = append(slice, element_1, element_2) 2 3new_slice = append(slice, 10, 20, 30)
Appending slice to slice
1// slice = append(slice, anotherSlice...) 2 3new_slice := append(slice_1, slice_2...)
Deleting from Slice #
To delete an element from a slice, we can just create a new slice which doesn’t contain the element to be deleted.
1arr := [5]int {10, 20, 30, 40, 50}
2
3i := 2 // index of element to be deleted
4
5slice_1 := arr[:i] // [10, 20]
6slice_2 := arr[i+1:] // [40, 50]
7
8new_slice := append(slice_1, slice_2...)
9// Now new_slice consists of [10, 20, 40, 50] and the element at index 2 got
10// deleted
Copy from a Slice #
1// func copy(dst, src []Type) int
2// num := copy(dest_slice, src_slice)
3
4src_slice := []int{10, 20, 30, 40, 50}
5dest_slice := make([]int, 3)
6
7num := copy(dest_slice, src_slice)
Maps in Go #
Maps are unordered collection of key-value pairs, implemented using hash tables.
- Zero value of a map is
nil
. - Length of a map can be calculated using
len()
function.
1// var <map_name> map[<key_data_type>]<value_data_type>
2var my_map map[string]int // creates a nil map
3
4// <map_name> := map[<key_data_type>]<value_data_type>{<key_value_pairs>}
5my_map := map[string]string{"h": "ello", "w": "orld"}
6
7// <map_name> := make(map[<key_data_type>]<value_data_type>, <initial_capacity>)
8my_map := make(map[string]int)
Access Items of Map #
- Access value using key
1my_map := map[string]string{"h": "ello", "w": "orld"}
2
3fmt.Println(my_map["h"]) // Output : ello
- Getting a key
1// value, found := map_name[key]
2my_map := map[string]string{"h": "ello", "w": "orld"}
3
4value, found := my_map["h"]
5fmt.Println(found, value) // Output: true, ello
Adding/Deleting a key-value pair #
1my_map := map[string]string{"h": "ello", "w": "orld"}
2
3my_map["t"] = "est" // adds a key-value pair
4
5fmt.Println(my_map) // Output: map[h:ello w:orld t:est]
6
7delete(my_map, "w") // deletes a key-value pair
8
9fmt.Println(my_map) // Output: map[h:ello t:est]
Clearing/Truncating a map #
Items can be removed one by one using a for loop or Reinitializing the map can also clear all items from the map.
1my_map := map[string]string{"h": "ello", "w": "orld"}
2my_map := make(map[string]string) // reinitializing with make truncates the map
Functions #
Functions are self-contained units of code which do a certain task, and help us divide a program into organisable and reusable chunks.
1// func <function_name>(<params>) <return_type> {
2// function body
3// }
4
5func addNumbers(a int, b int) int {
6 sum := a + b
7 return sum;
8}
9
10addNumbers(10, 20)
Return Type - A function can or can not have return value(s).
1// standard return values
2func operation(a int, b int) (int, int) {
3 sum := a + b
4 diff := a - b
5 return sum, diff
6}
7
8// named return values
9func operation(a int, b int) (sum int, diff int) {
10 sum := a + b
11 diff := a - b
12 return
13}
14
15func main() {
16 sum, difference := operation(20, 10)
17 fmt.Println(sum, " ", difference)
18}
Variadic functions #
Variadic functions have variable number of parameters.
1// func <function_name>(param1 type, param2 type, param3 ...type) <return type>
2
3func sumNumbers(numbers ...int) int
4
5func sumNumbers(str string, numbers ...int) int
The Variadic parameter numbers is a slice of all the arguments passed to the function.
1func printDetails(student string, subjects ...string) string {
2 fmt.Println("hi ", student, " /n Here are your subjects - ")
3 for _, sub := range subjects {
4 fmt.Println(sub, " ")
5 }
6 return sum
7}
8
9func main() {
10 fmt.Println(printDetails("Rohan", "English"))
11 fmt.Println(printDetails("Sohan", "Mathematics", "English", "Science"))
12}
Anonymous function #
Anonymous functions are functions declared without any named indentifier to refer to it.
1func main() {
2 x := func(l int, b int) int { // Anonymous function
3 return l * b
4 }
5 fmt.Printf("%T , %v", x, x(20, 50))
6}
7
8// Output: func(int, int) int, 1000
Defer Statement - A defer statement delays the execution of a function until the surrounding function returns.
1func printString(str string) {
2 fmt.Println(str)
3}
4func printNumber(int num) {
5 fmt.Println(num)
6}
7
8func main() {
9 defer printNumber(101) // delays the function execution
10 printString("GoLang")
11}
12
13// Output:
14// GoLang
15// 101
Pointers #
A Pointer is a variable that holds the memory address of another variable. It points to the address where the memory is allocated and provides ways to find or even change the value located at that address.
Address and Dereference operator #
&
operator The ampersand&
or address-of operator is used to get the address of a variable by prefixing the variable name with this operator.*
operator The asterisk*
or dereference operator is used to get the value at a memory location by prefixing the address with this operator.
1// Declaring a Pointer
2// var <pointer_name> *<data_type>
3var ptr_i *int
4var ptr_s *string
5
6// Initialising a Pointer
7// var <pointer_name> *<data_type> = &<variable_name>
8i := 10
9var ptr_i *int = &i // ptr is a pointer which holds the address of variable i
10
11// Shorthand
12s := "hello"
13ptr_s = &s
We can get or modify the value of a variable using *
operator on a pointer
that holds the address of that variable.
1s := "hello"
2ps := &s
3
4fmt.Println(s, *ps)
5
6*ps := "world"
7
8fmt.Println(s, *ps)
9
10// Output:
11// hello hello
12// world world
Passing by value #
Function is called directly passing the value of variable as argument, so modifying the variable inside the function doesn’t affect the original variable as the parameter is copied into another location of memory.
All basic data types such as int, bool, string etc are Pass by value by default.
1func modify(n int) {
2 n += 10
3}
4
5func main() {
6 num := 15
7 fmt.Println(num)
8 modify(num)
9 fmt.Println(num)
10}
11
12// Output:
13// 15
14// 15
Passing by Reference #
By using pointers, we can pass the references of variables as arguments and when their value is modified the value of original variable is also modified as the memory address of the original variable is passed using pointer.
Slices are references to an underlying Array so they are Pass by reference, Maps are also Pass by reference by default.
1func modify(n *int) {
2 *n += 10
3}
4
5func main() {
6 num := 15
7 fmt.Println(num)
8 modify(&num)
9 fmt.Println(num)
10}
11
12// Output:
13// 15
14// 25
Structs #
Structs are used to create custom data types by grouping various data elements together and referencing them through a single variable name.
Note: Structs are also Pass by value by default, so to modfiy values on Struct use pointer to follow Pass by reference.
Declaration and Initialisation #
1// Declaration
2// type <struct_name> struct {
3// list of fields
4// }
5
6type Student struct {
7 name string
8 rollNo int
9 marks []int
10 grades map[string]int
11}
12
13// Initialising
14// var <variable_name> <struct_name>
15var s Strudent
16
17// Initialising using new keyword
18// <variable_name> := new(<struct_name>)
19st := new(Strudent)
20
21st := Student{
22 name: "Rohan",
23 rollNo: 10,
24 marks: {80, 74, 96}
25}
Accessing Fields #
1// <variable_name>.<field_name>
2
3type Student struct {
4 name string
5 rollNo int
6 marks []int
7 grades map[string]int
8}
9
10var st Student
11
12st.name = "Sohan"
13fmt.Println(st.name, st.rollNo)
Equality : Two structs will be equal only if they are created from same Struct definition and they also have same values.
Method #
A Method is a function that has a defined reciever (extra parameter after func keyword).
1func (s Student) getMarks() float64 {
2 // code
3}
4func (s *Student) getMarks() float64 {
5 // code
6}
Example:
1type Circle struct {
2 radius float64
3 area float64
4}
5
6func (c *Circle) calcArea() {
7 c.area = 3.14 * c.radius * c.radius
8}
9
10func main() {
11 c := Circle{radius: 5}
12 c.calcArea()
13 fmt.Printf("%+v", c)
14}
15
16// Output: {radius:5 area:78.5}
Method sets #
A set of methods that are available to a data type and are useful to encapsulate functionality.
1type Student struct {
2 name string
3 grades []int
4}
5
6func (s *Student) displayName() {
7 fmt.Println(s.name)
8}
9
10func (s *Student) calculatePercentage() float64 {
11 sum := 0
12 for _, v := range s.grades {
13 sum += v
14 }
15 return float64(sum*100) / float64(len(s.grades)*100)
16}
17
18func main() {
19 s := Student{name: "Rohan", grades: []int{90, 75, 84}}
20 s.displayName()
21 fmt.Printf("%.2f%%", s.calculatePercentage())
22}
Interfaces #
An interface is like a blueprint for a method set. It specifies a method set and is a powerful way to introduce modularity in Go. It describes all the methods of a method set by providing the function signature for each method but does not implement them.
1// type <interface_name> interface {
2// method signatures
3// }
4
5type FixedDeposit interface {
6 getRateOfInterest() float64
7 calcReturn() float64
8}
Implementing Interface #
1type shape interface {
2 area() float64
3 perimeter() float64
4}
5
6type square struct {
7 side float64
8}
9
10func (s square) area() float64 {
11 return s.side * s.side
12}
13
14func (s square) perimeter() float64 {
15 return 4 * s.side
16}
17
18type rect struct {
19 length, breadth float64
20}
21
22func (r rect) area() float64 {
23 return r.length * r.breadth
24}
25
26func (r rect) perimeter() float64 {
27 return 2*(r.length+r.breadth)
28}
29
30func printData(s shape) {
31 fmt.Println(s)
32 fmt.Println(s.area())
33 fmt.Println(s.perimeter())
34}
35
36func main() {
37 r := rect{length: 3, breadth: 4}
38 s := square{side: 5}
39 printData(r)
40 printData(c)
41}
Final Remarks #
So, These were the important concepts of Golang, And I know this was a lot but if you made it till here, then Congrats 🥳.
If you liked this blog, found any mistakes or just want to say Hi, feel free to send an email or personal message me on X/Twitter.
Now, I will see you in the next blog. Until then keep learning and take care, Bye.