Въведение в Go

10.10.2018

Малко история

Малко история

C for the 21st century*

"Трите свята"

Резултатът

Други предимства?

Екосистема от добри open-source библиотеки и проекти

Стандартната библиотека

archive tar zip bufio builtin bytes compress bzip2 flate gzip lzw zlib
container heap list ring context crypto aes cipher des dsa ecdsa elliptic hmac
md5 rand rc4 rsa sha1 sha256 sha512 subtle tls x509 pkix database sql driver
debug dwarf elf gosym macho pe plan9obj encoding ascii85 asn1 base32 base64
binary csv gob hex json pem xml errors expvar flag fmt go ast build constant
doc format importer parser printer scanner token types hash adler32 crc32 crc64
fnv html template image color palette draw gif jpeg png index suffixarray io
ioutil log syslog math big bits cmplx rand mime multipart quotedprintable net
http cgi cookiejar fcgi httptest httptrace httputil pprof mail rpc jsonrpc smtp
textproto url os exec signal user path filepath plugin reflect regexp syntax
runtime cgo debug msan pprof race trace sort strconv strings sync atomic
syscall js testing iotest quick text scanner tabwriter template parse time
unicode utf16 utf8 unsafe

Научените уроци

Малко код

package workq

type Queue []*Item

func (q *Queue) Push(item *Item) {
    *q = append(*q, item)
    go item.Translate()
}

func (q *Queue) Pop() *Item {
    if !q.IsEmpty() {
        old := *q
        item := old[0]
        <- item.Done
        *q = old[1:len(old)]
        return item
    }
    return nil
}

func (q *Queue) Len() int {
    return len(*q)
}

Инсталация

* Linux, FreeBSD

Инсталирате си пакета go от вашия пакетен мениджър

* Mac OSX

* Windows

По време на курса ще използваме версия 1.11.1

Инсталация, епизод 2

1. Създавате директория ~/go
2. Слагате следните два реда в ~/.profile

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

* За Windows-аджийте има уловки:

Hello, world!

За да сме сигурни, че сме направили всичко като хората, създаваме файл hello.go:

package main

import "fmt"

func main() {
    fmt.Printf("Hello, world!\n")
}

и изпълняваме

go run hello.go

Как да пишем код на Go?

Имаме инструментът go, който се грижи за достатъчно много неща.
Повечето настройки се правят с environment variables.

Имаме workspace папка с три основни директории:

$ go

build       compile packages and dependencies
clean       remove object files and cached files
doc         show documentation for package or symbol
env         print Go environment information
bug         start a bug report
fix         update packages to use new APIs
fmt         gofmt (reformat) package sources
generate    generate Go files by processing source
get         download and install packages and dependencies
install     compile and install packages and dependencies
list        list packages
run         compile and run Go program
test        test packages
tool        run specified go tool
version     print Go version
vet         report likely mistakes in packages

Environment variables

$ go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/user/go"
GORACE=""
GOROOT="/usr/lib/go"
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"
CXX="g++"
CGO_ENABLED="1"

workspace

bin/
    hello                          # command executable
    outyet                         # command executable
pkg/
    linux_amd64/
        github.com/golang/example/
            stringutil.a           # package object
src/
    github.com/golang/example/
        .git/                      # Git repository metadata
        hello/
            hello.go               # command source
        outyet/
            main.go                # command source
            main_test.go           # test source
    golang.org/x/image/
        .git/                      # Git repository metadata
        bmp/
            reader.go              # package source
            writer.go              # package source
    ... (many more repositories and packages omitted) ...

Браво. И сега какво?

Връщаме се обратно към Hello, world! примера:

package main

import "fmt"

func main() {
    fmt.Printf("Hello, world!\n")
}

и се фокусираме върху

package main

Пакети

Една програма на Go е структура от пакети. Нямате шанс просто да хвърлите
едно парче код в даден файл и то да тръгне.

Особености

Без значение от колко файла се състои:

One package to rule them all

Ако си мислите за "Lord of the rings", грешите. Говорим ви за main.

В една програма имаме точно един пакет main и при изпълнението ѝ се изпълнява функцията main в него.

Програмата приключва, когато приключи изпълнението на функцията main.

Тя не връща нищо.

Преизползване на пакети

Става с ключовата думичка import, последвана от името на пакета, в кавички

import "fmt"

Ако искаме да импортнем няколко пакета:

import "fmt"
import "os"

което go fmt би свел до

import (
    "fmt"
    "os"
)

имаме и такива магии:

import "github.com/keltia/leftpad"

wow! much import! how to dependency?

import

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Printf("Hello, world!\n")
}

Видимост

Какво точно можем да използваме от импортнат пакет? Тук става забавно.

Даден идентификатор (било то на променлива, константа, тип, функция или
метод) е видим извън пакета си, тогава и само тогава когато името му започва с главна буква.

Наричаме ги exported (на други места им викат public).

Останалите са недостъпни (демек private)

Основни типове

... но това не е всичко

Независещи от архитектурата числови типове

Имаме свободата да решим точно колко байта да е нашия тип:

... и разбира се имаме unsigned:

Зависещи от архитектурата

Пояснение: типовете int и int32 са различни дори в архитектури на които int е 32 бита. Същото важи и за uint и uint32.

Пример

var name string = "Лили Иванова"
var age uint8 = 25

Горното можем (и трябва) да запишем като:

var (
    name string = "Лили Иванова"
    age uint8 = 25
)

Ако са с един и същи тип, можем и така:

var name, age string = "Лили Иванова", "двадесет и пет"

WTF! WTF! WTF! Защо типът е на грешното място!?

type-inference

Можем да правим и така:

name := "Лили Иванова"
age := 25
package main

import "fmt"

name := "Киро"

func main() {
    fmt.Printf("%s, защо да не може?\n", name)
}

Кастване

Кастването работи, както очаквате:

number := 42                      // (int=42)
specificNumber := float64(number) // (float64=42.0)

С тази разлика, че нямате имплицитен каст:

package main

func main() {
    number := 42
    specificNumber := float64(number)
    println(number * specificNumber) // (mismatched types int and float64)
}

Strings and runes

package main

import "fmt"

func main() {
    fmt.Printf("We have a cool fmt package: %c %c!\n", '\u2713', '☢')
    fmt.Printf("こんにちは世界!\n")
    fmt.Printf(`With backticks we can ignore backslashes like \n!`)
}

Стойности по подразбиране

Не се грижим за това да си инициализираме стойностите. Всяка променлива в Go се инициализира по подразбиране:

var (
    digit int         // 0
    digit *int        // nil
    number float64    // 0.0
    isIt bool         // false
    name string       // ""
    root complex64    // (0+0i)
    pipe chan <type>  // nil
)

Инициализация

package main

import "fmt"

var subject string

func init() {
    subject = "world"
}

func main() {
    fmt.Printf("Hello, %s!\n", subject)
}

За разлика от нормалните функции, за които ще си говорим следващия път, можем да имаме няколко init() функции в един пакет

Unused variable

Дефинирана стойност, която не се използва, води до грешка по време на компилация

package main

import "fmt"

func main() {
    name := "Кевин"
    fmt.Printf("Май забравихме някой?\n")
}

Константи

Аналогично с var:

const (
    name string = "Лили Иванова"
    age uint8 = 25
)

Могат да бъдат само

iota

Нали помните enum?

const (
    Monday = iota
    Tuesday
    Wednesday
    Thursday
    Friday
    Partyday
    Sunday
)

Малко по-сложен пример :)

package main

import "fmt"

const (
    B  uint64 = iota + 1
    KB        = 1 << (10 * iota)
    MB
    GB
    TB
)

func main() {
    fmt.Printf("Result is %d bytes", 2*KB)
}

Контролни структури

if

if age < 13 {
    fmt.Print("Още не си тийнейджър")
} else if age >= 13 && age < 20 {
    fmt.Print("В момента си тийнейджър")
} else {
    fmt.Print("Минали са ти тийнейджърските години")
}

for

Добрия стар for от C:

for i := 0; i < 20; i++ {
    fmt.Println(i)
}

И точно както в C, някои от аргументите не са задължителни.
... някои да се чете като всички.

package main

import "fmt"

func main() {
    for {
        fmt.Println("Can't stop me!")
    }
}

Забележка: Отварящата { и тук е на същия ред с условието

while

Няма. for покрива всичките му приложения.

foreach

Няма. for покрива всичките му приложения.

switch

switch {
case age < 13:
    fmt.Print("Още не си тийнейджър")
case age >= 13 && age < 20:
    fmt.Print("В момента си тийнейджър")
default:
    fmt.Print("Минали са ти тийнейджърските години")
}

Няма нужда от излишния break

Ако искаме да изпълним два поредни case-а, използваме fallthrough.

break и continue

Все пак има break, който е приложим за for цикли.

continue работи, точно както очаквате и където очаквате.

goto

Да, има и такова.

Ако не знаете кога, не го ползвайте.

Към момента допускаме, че още не знаете кога => не го ползвате.

Особености, с които се сблъскахте (Q&A)

;

Q: Няма точка и запетая след всеки израз?

A: Всъщност има, но е имплицитна.

Сега си спомнете за правилото с отварящата {.

Индентация

Q: Интервали vs. Табове?
A: Един таб.

За четене

Q: Нещо за четене? Книги? Туториъли?
A:

В следващия епизод:

Въпроси?