Getting Started with GoLang. Based on this Udemy course.
package main
import "fmt"
func main(){
fmt.Println("Hi there!")
}
- Package is a collection of common source code files
- Its like a project or workspace
- One app typically has one package
- A apackage can have many files in it
- Every file in the package must say which package it belongs to
package main
There are two types of packages:
- Executable
- Generates a file that we can run
- Used for actually doing something (eg: run a server)
- An executable package is called main....package main is sacred
- Package main must always have a main function
- Reusable
- Code used as helpers
- Good place to put reusable logic
- Importing libraries to do stuff main can't do
- Golang standard packages
- Functions just like functions in any other programming language
A package that can do the following:
- Go is statically typed vs dynamically typed eg Python, JS
- We need to define a type for a variable
package main
import "fmt"
func main(){
var card string = "Ace of spades"
fmt.Println(card)
}
package main
import "fmt"
func main(){
// var card string = "Ace of spades"
var card:= "Ace of spades"
fmt.Println(card)
}
- For reassigning value we don't have to use a colon
- Need to define
package main
import "fmt"
func main(){
var card = newCard()
fmt.Println(card)
}
func newCard() string {
return "Five of Diamonds"
}
- Needs a type defined as well
package main
import "fmt"
func main(){
cards := []string{newCard(), newCard(), "Ace of Diamonds"}
}
func newCard() string {
return "Five of Diamonds"
}
- Appending: returns a new slice
package main
import "fmt"
func main(){
cards := []string{newCard(), newCard(), "Ace of Diamonds"}
cards = append(cards, "Six of spades")
fmt.Println(cards)
}
func newCard() string {
return "Five of Diamonds"
}
package main
import "fmt"
func main(){
cards := []string{newCard(), newCard(), "Ace of Diamonds"}
cards = append(cards, "Six of spades")
for i, card := range cards {
fmt.Println(i, card)
}
}
func newCard() string {
return "Five of Diamonds"
}
package main
//Create a new type of 'deck'
//which is a slice of strings
type deck []string
package main
import "fmt"
func main(){
cards := deck{newCard(), newCard(), "Ace of Diamonds"}
cards = append(cards, "Six of spades")
for i, card := range cards {
fmt.Println(i, card)
}
}
func newCard() string {
return "Five of Diamonds"
}
package main
import "fmt"
//Create a new type of 'deck'
//which is a slice of strings
type deck []string
func (d deck) print(){
for i, card := range d{
fmt.Println(i, card)
}
}
package main
// import "fmt"
func main(){
cards := deck{newCard(), newCard(), "Ace of Diamonds"}
cards = append(cards, "Six of spades")
cards.print()
}
func newCard() string {
return "Five of Diamonds"
}
- We use "_" to let go know that we created a variable but we don't intend to use it
package main
import "fmt"
//Create a new type of 'deck'
//which is a slice of strings
type deck []string
func newDeck() deck {
cards := deck{}
cardSuits := []string{"Spades", "Hearts", "Diamonds", "Clubs"}
cardValues := []string{"Ace", "Two", "Three", "Four"}
for _, suit := range cardSuits{
for _, value := range cardValues{
cards = append(cards, value+" of "+suit)
}
}
return cards
}
func (d deck) print(){
for _, card := range d{
fmt.Println(card)
}
}
//return two different slices
func deal(d deck, handSize int) (deck, deck){
return d[:handSize], d[handSize:]
}
func main(){
cards := newDeck()
hand, remainingDeck := deal(cards, 5)
hand.print()
remainingDeck.print()
}
- deck->string->byte slice
func (d deck) toString() string{
// []string(d) //convert to slice of string
return strings.Join([]string(d), ",")
}
- byte slice->string->deck
func newDeckFromFile(filename string) deck {
bs, err := ioutil.ReadFile(filename)
if err != nil {
// Option 1 - log the error and default to newDeck()
// Option 2 - log the error and quit program
fmt.Println("Error:", err)
os.Exit(1)
}
s := strings.Split(string(bs), ",")
return deck(s)
}
- Pseudo random
func (d deck) shuffle() {
for i := range d {
newPosition := rand.Intn(len(d) - 1)
d[newPosition], d[i] = d[i], d[newPosition]
}
}
- Use time -> use it as seed value -> use new Rand
func generateRandomNum(l int) int {
source := rand.NewSource(time.Now().UnixNano())
r := rand.New(source)
return r.Intn(l)
}
package main
import (
"os"
"testing"
)
func TestNewDeck(t *testing.T) {
d := newDeck()
// Check if length of new deck is correct
if len(d) != 16 {
t.Errorf("Expected deck length of 16, but got %v", len(d))
}
// Check the last card is 4 of clubs
if d[len(d)-1] != "Four of Clubs" {
t.Errorf("Expected last card to be Four of Clubs but got %v", d[len(d)-1])
}
}
func TestSaveToDeckAndDeckFromFile(t *testing.T) {
os.Remove("_decktesting")
d := newDeck()
d.saveToFile("_decktesting")
loadedDeck := newDeckFromFile("_decktesting")
if len(loadedDeck) != 16 {
t.Errorf("Expected 16 but got %v", len(loadedDeck))
}
os.Remove("_decktesting")
// loadedDeck
}
package main
type person struct {
firstName string
lastName string
}
func main() {
}
- Nasty
package main
type person struct {
firstName string
lastName string
}
func main() {
alex := person{"Alex", "Anderson"}
}
- Okay
package main
type person struct {
firstName string
lastName string
}
func main() {
alex := person{firstName: "Alex", lastName: "Anderson"}
}
package main
import "fmt"
type person struct {
firstName string
lastName string
}
func main() {
var alex person
// alex := person{firstName: "Alex", lastName: "Anderson"}
fmt.Println(alex)
}
$ {firstName: lastName:}
package main
import "fmt"
type person struct {
firstName string
lastName string
}
func main() {
var alex person
// alex := person{firstName: "Alex", lastName: "Anderson"}
alex.firstName = "Alex"
alex.lastName = "Anderson"
fmt.Println(alex)
fmt.Printf("%+v", alex)
fmt.Println("")
}
package main
import "fmt"
type person struct {
firstName string
lastName string
contact contactInfo
}
type contactInfo struct {
email string
zipCode string
}
func main() {
jim := person{
firstName: "Jim",
lastName: "Party",
contact: contactInfo{
email: "jim@gmail.com",
zipCode: "120514",
},
}
fmt.Printf("%+v", jim)
}
- Also valid
package main
import "fmt"
type person struct {
firstName string
lastName string
contactInfo
}
type contactInfo struct {
email string
zipCode string
}
func main() {
jim := person{
firstName: "Jim",
lastName: "Party",
contactInfo: contactInfo{
email: "jim@gmail.com",
zipCode: "120514",
},
}
fmt.Printf("%+v", jim)
}
package main
import (
"fmt"
)
type person struct {
firstName string
lastName string
contactInfo
}
type contactInfo struct {
email string
zipCode string
}
func main() {
jim := person{
firstName: "Jim",
lastName: "Party",
contactInfo: contactInfo{
email: "jim@gmail.com",
zipCode: "120514",
},
}
jim.print()
}
func (p person) print() {
fmt.Printf("%+v", p)
}
package main
import (
"fmt"
)
type person struct {
firstName string
lastName string
contactInfo
}
type contactInfo struct {
email string
zipCode string
}
func main() {
jim := person{
firstName: "Jim",
lastName: "Party",
contactInfo: contactInfo{
email: "jim@gmail.com",
zipCode: "120514",
},
}
jim.updateName("Aish")
jim.print()
}
func (p person) print() {
fmt.Printf("%+v", p)
}
func (p person) updateName(newFirstName string) {
p.firstName = newFirstName
}
- Go does pass by value
- How to do it correctly
package main
import (
"fmt"
)
type person struct {
firstName string
lastName string
contactInfo
}
type contactInfo struct {
email string
zipCode string
}
func main() {
jim := person{
firstName: "Jim",
lastName: "Party",
contactInfo: contactInfo{
email: "jim@gmail.com",
zipCode: "120514",
},
}
jimPointer := &jim
jimPointer.updateName("Aish")
jim.print()
}
func (p person) print() {
fmt.Printf("%+v", p)
}
func (pointerToPerson *person) updateName(newFirstName string) {
(*pointerToPerson).firstName = newFirstName
}
package main
import (
"fmt"
)
type person struct {
firstName string
lastName string
contactInfo
}
type contactInfo struct {
email string
zipCode string
}
func main() {
jim := person{
firstName: "Jim",
lastName: "Party",
contactInfo: contactInfo{
email: "jim@gmail.com",
zipCode: "120514",
},
}
jim.updateName("Aish")
jim.print()
}
func (p person) print() {
fmt.Printf("%+v", p)
}
func (pointerToPerson *person) updateName(newFirstName string) {
(*pointerToPerson).firstName = newFirstName
}
- Seems like there is no need for pointers to do a 'deep copy' sort of thing
- Arrays vs slices
- Reference Types do not require pointers for deep copy sort of functionality, you can alter them directly
- Simple
package main
import "fmt"
func main() {
colors := map[string]string{
"red": "12039i10239i01923",
"green": "12039uijndkcjn",
}
}
- 0 value initialization
package main
import "fmt"
func main() {
var colors map[string]string
// colors := map[string]string{
// "red": "12039i10239i01923",
// "green": "12039uijndkcjn",
// }
fmt.Printf("%+v", colors)
}
- Another 0 init
package main
import "fmt"
func main() {
colors := make(map[string]string)
// var colors map[string]string
// colors := map[string]string{
// "red": "12039i10239i01923",
// "green": "12039uijndkcjn",
// }
fmt.Printf("%+v", colors)
}
package main
import "fmt"
func main() {
colors := make(map[string]string)
// var colors map[string]string
// colors := map[string]string{
// "red": "12039i10239i01923",
// "green": "12039uijndkcjn",
// }
colors["white"] = ";wdkjnaksjdnfajsndf;ijns"
fmt.Printf("%+v", colors)
}
- Maps dont have . syntax like structs
- Delete
package main
import "fmt"
func main() {
colors := map[string]string{
"red": "wkdna;djnf;kasdjn",
"white": "asijdnpaijsdnfaijsdnf",
"green": "aksdjnc;kajsnd;kajn",
}
printMap(colors)
}
func printMap(c map[string]string) {
for color, hex := range c {
fmt.Println(color, hex)
}
}
- Interfaces make it easier to re-use code for different types without having to re-type
- Bad way of writing since need to repeat the printGreeting function
package main
import "fmt"
type englishBot struct{}
type spanishBot struct{}
func main() {
eb := englishBot{}
sb := spanishBot{}
}
func (englishBot) getGreeting() string {
return "Hi there!"
}
func printGreeting(eb englishBot) {
fmt.Println(eb.getGreeting())
}
func printGreeting(sb spanishBot) {
fmt.Println(sb.getGreeting())
}
func (spanishBot) getGreeting() string {
return "Hola Amigo!"
}
- Refactored to use interfaces
package main
import "fmt"
type englishBot struct{}
type spanishBot struct{}
type bot interface {
getGreeting() string
}
func main() {
eb := englishBot{}
sb := spanishBot{}
printGreeting(eb)
printGreeting(sb)
}
func (englishBot) getGreeting() string {
return "Hi there!"
}
// func printGreeting(eb englishBot) {
// fmt.Println(eb.getGreeting())
// }
// func printGreeting(sb spanishBot) {
// fmt.Println(sb.getGreeting())
// }
func printGreeting(b bot) {
fmt.Println(b.getGreeting())
}
func (spanishBot) getGreeting() string {
return "Hola Amigo!"
}
- Explanation
- In other words interfaces allow englishBot and spanishBot to have another type called bot, as long as they have a getGteeting function that returns string
- We can define both the input and output type of the functions we want to be interfaced
- We can define multiple functions in the interface
type bot interface {
getGreeting() string
getVersion() string
respondToUser(user) string
}
- We cannot createa an 'object'/value from an interface type. We can only do that using concrete types
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
resp, err := http.Get("http://google.com")
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
fmt.Println(resp)
}
- We can 'nest'/embed interfaces within one another
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
resp, err := http.Get("http://google.com")
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
bs := make([]byte, 999999)
resp.Body.Read(bs)
fmt.Println(string(bs))
}
- Doing the above in one line using io.Copy
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
resp, err := http.Get("http://google.com")
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
io.Copy(os.Stdout, resp.Body)
}
- Without routines and channels
package main
import (
"fmt"
"net/http"
)
func main() {
links := []string{
"http://google.com",
"http://facebook.com",
"http://stackoverflow.com",
"http://golang.org",
"http://amazon.com",
}
for _, link := range links {
checkLink(link)
}
}
func checkLink(link string) {
_, err := http.Get(link)
if err != nil {
fmt.Println(link, "might be down!")
return
}
fmt.Println(link, "is up!")
}
-
If a call is blocking, control is passed back to main routine so that some other go routine can be spun up and do its thing
package main
import (
"fmt"
"net/http"
)
func main() {
links := []string{
"http://google.com",
"http://facebook.com",
"http://stackoverflow.com",
"http://golang.org",
"http://amazon.com",
}
for _, link := range links {
go checkLink(link)
}
}
func checkLink(link string) {
_, err := http.Get(link)
if err != nil {
fmt.Println(link, "might be down!")
return
}
fmt.Println(link, "is up!")
}
- This doesn't work because the main go routine ends before the child routines complete their work
- We need to use channels - way to communicate between go routines
- Allows communication between go routines
- Channels are typed so all messages sent to a channel must be of the same type
- Receiving messages from a channel is a blocking call
package main
import (
"fmt"
"net/http"
)
func main() {
links := []string{
"http://google.com",
"http://facebook.com",
"http://stackoverflow.com",
"http://golang.org",
"http://amazon.com",
}
c := make(chan string) //create a blank channel
for _, link := range links {
go checkLink(link, c)
}
fmt.Println(<-c)
}
func checkLink(link string, c chan string) {
_, err := http.Get(link)
if err != nil {
fmt.Println(link, "might be down!")
c <- "Might be down I think"
return
}
fmt.Println(link, "is up!")
c <- "Yup its up"
}
Output:
http://google.com is up!
Yup its up
package main
import (
"fmt"
"net/http"
)
func main() {
links := []string{
"http://google.com",
"http://facebook.com",
"http://stackoverflow.com",
"http://golang.org",
"http://amazon.com",
}
c := make(chan string) //create a blank channel
for _, link := range links {
go checkLink(link, c)
}
for i := 0; i < len(links); i++ {
fmt.Println(<-c)
}
}
func checkLink(link string, c chan string) {
_, err := http.Get(link)
if err != nil {
fmt.Println(link, "might be down!")
c <- "Might be down I think"
return
}
fmt.Println(link, "is up!")
c <- "Yup its up"
}
- Keep checking
package main
import (
"fmt"
"net/http"
)
func main() {
links := []string{
"http://google.com",
"http://facebook.com",
"http://stackoverflow.com",
"http://golang.org",
"http://amazon.com",
}
c := make(chan string) //create a blank channel
for _, link := range links {
go checkLink(link, c)
}
for {
go checkLink(<-c, c)
}
}
func checkLink(link string, c chan string) {
_, err := http.Get(link)
if err != nil {
fmt.Println(link, "might be down!")
c <- link
return
}
fmt.Println(link, "is up!")
c <- link
}
- Infinite for loop
for {
go checkLink(<-c, c)
}
same as
for l := range c{
go checkLink(l, c)
}
- for i in range
for i := 0; i < len(links); i++ {
fmt.Println(<-c)
}
for l := range c {
go func() {
time.Sleep(5 * time.Second)
checkLink(l, c)
}()
}
- This will not work because l is defined outside the scope of the functional literal
- Correct way of doing it
for l := range c {
go func(l string) {
time.Sleep(5 * time.Second)
checkLink(l, c)
}(l)
}
- We never try to share variables between go routines (especially between main and child routines)
- We always either pass them as variables or we use channels