Hello World
Как-то появилась причина попробовать пописать на Go. На тот момент я знал, что это язык от Google, язык молодой, язык компилируемый, вроде как активноразвивающийся и с зарплатами выше средних. Неплохой набор.
В первой попавшейся статье узнаем, что Go к тому же легкий в изучении. Интересно, сколько PHP-программистов стало PHP-программистами, потому что PHP легкий в изучении? И действительно, за пару вечеров можно уже неплохо ориентироваться в языке.
Итак, ищем какой-нибудь golang roadmap, небольшое количество времени, и вот он, helloworld на Golang. Теперь надо его запустить. Сама установка Go - быстрая и простая, занимает пару минут(скачать, нажать далее несколько раз), так что смотрим пример:
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
Первый взгляд
Вместо пространств имен из PHP, здесь пакеты. Смысл немного похожий, но Golang-компоузер идет сразу из коробки. Что еще сразу же бросится в глаза, стоит написать пару строчек кода, так это автоформатирование. PSR, который про оформление кода, также получаем из коробки. И это здорово.
В ознакомительных статьях по Golang можно встретить такое заявление разработчиков: "Если что-то можно сделать на Go, то это можно сделать единственным способом". Громкое утверждение, которое, конечно, не надо воспринимать буквально, но по мере знакомства с языком, можно подмечать такие моменты. Автоформатирование как раз один из них.
Итак у нас есть простая установка, менеджер зависимостей и автоформатирование. Что дальше?
Ошибки
Например работа с ошибками. В Golang нет исключений, зато функции и методы могут возвращать несколько значений. Одно из возвращаемых значений принято использовать для передачи информации об ошибках.
package main
import (
"errors"
"fmt"
)
// f1 returns two values
func f1(arg int) (int, error) {
if arg == 42 {
return -1, errors.New("can't work with 42")
}
return arg + 2, nil
}
func main() {
num, err := f1(42)
if err != nil {
panic(err)
}
fmt.Println(num)
}
Да, как-то так, сюда надо добавить, что если объявить переменную в Go, то ее необходимо использовать, иначе код просто не скомпилируется. Язык подталкивает обрабатывать ошибки, хотя и оставляет возможность этого не делать.
Выше я написал, что в Golang нет исключений, это не совсем так, в Go есть стандартная функция panic, которая при вызове прервет текущее выполнение и начнет "всплывать" наверх по аналогии с исключениями. По аналогии с исключениями panic можно перехватить, обработать и продолжить выполнение программы.
И еще немного про ошибки, вроде язык и подталкивает к их обработке, но также Go предлагает конструкцию пустого идентификатора(Blank identifier), используя его, можно не создавать новую переменную под возвращаемые функцией значения, следовательно, не нужно будет ошибку обрабатывать.
func main() {
num, _ := f1(42)
fmt.Println(num)
}
Получается, если захотеть, то можно и не обрабатывать, но вряд ли это останется незамеченным на каком-нибудь код ревью.
Рутина
Go - язык строготипизированный, плюсы понятны, но это же придется возиться с типами? Да. К счастью, кое-где разработчики Go насыпали сахарку. Например, при создании новой переменной, можно использовать оператор := и тогда указывать её тип не придется.
Второй момент - это работа с массивами. Да, всепоглощающего array из PHP здесь нет, нужно будет вспомнить конструкции вида [][]string, но вот с выделением памяти уже подсобили, в Go есть тип Slice, который сильно упрощает эту задачу.
func main() {
//create slice with len 1
myslice := make([]int, 1)
for i := 1; i < 10; i++ {
// add necessary amount of elements
myslice = append(myslice, i)
}
//print slice with 11 elements
fmt.Println(myslice)
}
А вот удалять неиспользуемые данные будет сборщик мусора. Из рантайма. Из рантайма еще будет безопасность для указателей. Но, собственно, в Go есть рантайм, без него не поедем.
ООП
В Go нет наследования классов, привычного PHP-программисту. Вместо наследования - нечто вроде композиции. При этом мы не можем использовать "наследника" вместо "родителя". Но можем реализовывать интерфейс, и использовать уже его. Получается полиморфизм только на интерфейсах.
Стоит добавить, что реализация интерфейса в Go неявная, не нужно использовать аналог implements, достаточно чтобы "класс" содержал методы из интерфейса. Здесь класс написан в кавычках, потому что в Go нет привычных нам классов. Зато есть структуры и возможность привязывать к ним функции. Вот как это может выглядеть:
// our new type
type Type1 float64
// Golang way for method's definition
func (t1 Type1) Show() {
fmt.Println(t1)
}
// methods and properties of Type1 accessible over Type2
type Type2 struct {
Type1
}
// redefine Show method
func (t2 Type2) Show() {
fmt.Println(t2.Type1 + 7)
}
func myF1(t1 Type1) {
t1.Show()
}
// our new types have implemented this interface just because
// they contain Show method
type Shower interface {
Show()
}
func myF2(s Shower) {
s.Show()
}
func main() {
t1 := Type1(54)
t1.Show() //54
t2 := Type2{Type1: 45}
t2.Show() //52
//we can access method of "parent"
t2.Type1.Show() //45
myF1(t1) //54
// will not be compiled, we cant pass Type2 instead Type1
//Show(t2)
myF2(t1) //54
myF2(t2) //52
}
И про инкапсуляцию, в рамках одного пакета (это не прямая аналогия с пакетами composer'a, пакет в Go - это один или несколько файлов, объединенных по какому-то смыслу. Из пакетов в Golang получается модуль. Модуль - это уже что-то похожее на пакет из composer'а) у нас есть доступ ко всем свойствам и методам наших типов. А при использовании одного пакета в другом пакете будут доступны только свойства и методы начинающиеся с большой буквы. С маленькой буквы - это аналог private. А protected доступа нет, т.к нет наследования.
Многопоточность
Цирковой номер - PHP-программист рассказывает про много��оточность. И все же, многопоточность является одной из выделяемых особенностей Golang. Легко можно создать несколько новых потоков, наладить между ними взаимодействие, установить контроль над данными, чтобы избежать гонки.
Но как я понял из пары-двойки статей про многопоточность - это не палочка-выручалочка по увеличению производительности. Бездумно нарастить количество потоков не получится, даже если твоя задача хорошо распараллеливается, то все может упереться в специфичность окружения, и выигрыш в производительности окажется не таким уж впечатляющим.
И продолжая тему производительности. Хоть язык и компилируемый, но у него есть рантайм со сборщиком мусора и безопасностью. И судя по паре-двойке статей, где сравнивалась производительность Go с другими языками, получается что-то такое: в большинстве приведенных в статьях тестов, Go обгонял интерпретируемые языки, в большинстве тестов был быстрее Java, но ни в одном приведенном тесте не был быстрeе C, C++, Rust.
Сообщество
Язык молодой, людей в нем не то, чтобы много, соответственно готовых решений под твои потребности может и не найтись. Готовых ответов на твой вопрос может и не найтись и т.д. Если в рамках микросервисных задач материалов достаточно, то если отойти в сторону какого-нибудь AI, будет куда сложнее. В последние несколько лет рост Go замедляется.
Выводы
У Golang есть целый ряд плюсов. Один из главнейших, на мой взгляд, это простота в изучении. Много вещей идут сразу из коробки. Язык от Google. Писать на Go просто приятно. Концепции, заложенные авторами, позволяют под новым углом взглянуть на привыч��ые вещи. Но хватит ли этого, чтобы попасть и закрепиться в "топах" - вопрос открытый.
P.S. Подозреваю, что в статье есть неточности или даже откровенные ошибки. Если ты их заметил, помоги тому, кто мог не заметить - оставь комментарий.