Chapter02 - pslpune/golang-jumpstart GitHub Wiki
Basic data types
In this chapter we familiarize ourselves with the basic in-built types in Go. Using small code snippets we get an idea of what can be done with the types and what is forbidden. Strings is interesting, while other types could be a walkthru.
Strings
An immutable sequence of bytes, including bytes with value 0 but usually contain human readable text. Text is interpretted as sequences of unicode code points - runes
package main
import (
"fmt"
)
func main() {
name := "Hello World"
fmt.Println(name)
}
The above program prints Hello World - Strings in Go are Unicode compliant and are encoded in UTF-8 format
package main
import (
"fmt"
)
func printBytes(s string) {
fmt.Printf("Bytes: ")
for i := 0; i < len(s); i++ { //len(s) returns the number of bytes
fmt.Printf("%x ", s[i]) // check out the formatter
}
}
func main() {
name := "Hello World"
fmt.Printf("String: %s\n", name) // check out this formatter - this is used for printing string as is in text
printBytes(name)
}
Just by changing the format specifier this can print the same string in the byte format.(underlying byte array) - __as contarary to belief len(s) actually gets the length of the bytes and not characters. This is odd, Isn't one character = one byte ? , Yes & No not always.
Till we are with English / Latin characters the statement above shall hold true. With other characters like ñ are composite and greater than 1 byte is required to hold it in memory. But for now here is the output of the above snippet.
String: Hello World
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
Although printing characters came out to be as expected in the above example, below is a snippet that does not go as per expectations.
package main
import (
"fmt"
)
func printBytes(s string) {
fmt.Printf("Bytes: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
fmt.Printf("Characters: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%c ", s[i])
}
}
func main() {
name := "Hello World"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
fmt.Printf("\n\n")
name = "Señor"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
}
String: Hello World
Characters: H e l l o W o r l d
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
String: Señor
Characters: S e à ± o r
Bytes: 53 65 c3 b1 6f 72
The unicode representaion of ñ is U+00F1 and it occupies 2 bytes when represented in the memory. Hence we need a distinct type that can represent one displayed character - which Go calls as rune. Lets see Runes next
Rune
A rune is a builtin type in Go and it’s the alias of int32. Rune represents a Unicode code point in Go. It doesn’t matter how many bytes the code point occupies, it can be represented by a rune. Lets get the output of the same snippet above just that this time we use []rune as the type coverted to.
package main
import (
"fmt"
)
func printBytes(s string) {
fmt.Printf("Bytes: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
fmt.Printf("Characters: ")
runes := []rune(s)
for i := 0; i < len(runes); i++ {
fmt.Printf("%c ", runes[i])
}
}
func main() {
name := "Hello World"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
fmt.Printf("\n\n")
name = "Señor"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
}
String: Hello World
Characters: H e l l o W o r l d
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
String: Señor
Characters: S e ñ o r
Bytes: 53 65 c3 b1 6f 72
Creating strings from slice of bytes/runes
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{67, 97, 102, 195, 169}//decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
str := string(byteSlice)
fmt.Println(str)
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str = string(runeSlice)
fmt.Println(str)
}
Length of a string
As we have seen in prior section the len function actually calculates the length of bytes in a string and not the characters. This yeilds unexpected results when characters in the string are not strictly latin / english. Hence we have an alternate function under the utf-8 package RuneCountInString
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
word1 := "Señor"
fmt.Printf("String: %s\n", word1)
fmt.Printf("Length: %d\n", utf8.RuneCountInString(word1))
fmt.Printf("Number of bytes: %d\n", len(word1))
fmt.Printf("\n")
word2 := "Pets"
fmt.Printf("String: %s\n", word2)
fmt.Printf("Length: %d\n", utf8.RuneCountInString(word2))
fmt.Printf("Number of bytes: %d\n", len(word2))
}
String: Señor
Length: 5
Number of bytes: 6
String: Pets
Length: 4
Number of bytes: 4
String Comparison & Concatenation
The == operator is enough to compare 2 strings while + can help concatenate. Alternatively the fmt.Sprintf can also format and then concatenate. Below code snippets are self explanatory
package main
import (
"fmt"
)
func compareStrings(str1 string, str2 string) {
if str1 == str2 {
fmt.Printf("%s and %s are equal\n", str1, str2)
return
}
fmt.Printf("%s and %s are not equal\n", str1, str2)
}
func main() {
string1 := "Go"
string2 := "Go"
compareStrings(string1, string2)
string3 := "hello"
string4 := "world"
compareStrings(string3, string4)
// simple concatenation
string1 = "Go"
string2 = "is awesome"
result := string1 + " " + string2
fmt.Println(result)
// concatenation using fmt.Sprintf()
string1 := "Go"
string2 := "is awesome"
result := fmt.Sprintf("%s %s", string1, string2)
fmt.Println(result)
}
Go and Go are equal
hello and world are not equal
Go is awesome
Go is awesome
Strings are immutable
String literals arent mutable, in that once assigned a value it cannot be changed, string operations always create a new copy of the original string. If the usecase still demands the string has to be modified, a []rune slice can be used. rune slices are mutable.
package main
import (
"fmt"
)
func mutate(s string)string {
s[0] = 'a'//any valid unicode character within single quote is a rune
return s
}
func main() {
h := "hello"
fmt.Println(mutate(h))
}
package main
import (
"fmt"
)
func mutate(s []rune) string {
s[0] = 'a'
return string(s)
}
func main() {
h := "hello"
fmt.Println(mutate([]rune(h)))
}
Bool type
Boolean values in Go are referred to as bool and they (as expected) store either true or false
package main
import "fmt"
func main() {
a := true
b := false
fmt.Println("a:", a, "b:", b)
c := a && b
fmt.Println("c:", c)
d := a || b
fmt.Println("d:", d)
}
Numeric types
| Name | Size(Bits) | Description |
|---|---|---|
| int8 | 8 | represents 8 bit signed integers |
| int16 | 16 | represents 16 bit signed integers |
| int32 /rune | 32 | represents 32 bit signed integers |
| int64 | 64 | represents 64 bit signed integers |
| int | 32/64 | represents 32/64 bit signed integers, depending on the underlying platform |
| uint8 /byte | 8 | represents 8 bit unsigned integers |
| uint16 | 16 | represents 16 bit unsigned integers |
| uint32 | 32 | represents 32 bit unsigned integers |
| uint64 | 64 | represents 64 bit unsigned integers |
| uint | 32/64 | represents 32/64 bit unsigned integers, depending on the underlying platform |
| float32 | 32 | 32 bit floating point numbers |
| float64 | 64 | 64 bit floating point numbers |
Constants
The term constant in Go is used to denote fixed values such as below. The keyword const is used to declare a constant.
package main
import (
"fmt"
)
func main() {
const a = 50
fmt.Println(a)
}
There is also another syntax to define a group of constants using a single statement.
package main
import (
"fmt"
)
func main() {
const (
name = "John"
age = 50
country = "Canada"
)
fmt.Println(name)
fmt.Println(age)
fmt.Println(country)
}
John
50
Canada
Value of the constants can be assigned only once during every run of the program. Value of the constant has to be known during compile time
package main
func main() {
const a = 55 //allowed
a = 89 //reassignment not allowed
var a = math.Sqrt(4) //allowed
const b = math.Sqrt(4) //not allowed, since the RHS expressio is evaluated during runtime
}
Untyped constants
package main
import (
"fmt"
)
func main() {
const n = "Sam"
var name = n
fmt.Printf("type %T value %v", name, name)
}
Go is a strongly typed langauge and whether its explicit specification of the type or deduction of the type from the RHS expression, whats a fact is Go needs the type of the variable at compile time.
var name = n should have then resulted in compile time error?
The answer is untyped constants have a default type associated with them and they supply it if and only if a line of code demands it.