3 наиболее распространённых подводных камня в Go

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


2020-06-05 14:01

разработка по

Начиная изучение Go, многие сталкиваются с совершенно не очевидными моментами в этом языке. Рассмотрим три таких подводных камня в Go.

3 наиболее распространённых подводных камня в Go

1. Range

Начнем программирование на Go с основ. Функция range является одной из самых используемых в Go. Вот пример использования (не обращайте внимания, что мы присваиваем всем животным в зоопарке 999 ног):

type Animal struct {  	name string  	legs int  }    func main() {    zoo := []Animal{ Animal{ "Dog", 4 },                     Animal{ "Chicken", 2 },                     Animal{ "Snail", 0 },                   }      fmt.Printf("-> Before update %v ", zoo)      for _, animal := range zoo {      // ? Oppps! `animal` is a copy of an element ?      animal.legs = 999    }      fmt.Printf(" -> After update %v ", zoo)  }

Вышеприведённый код выглядит довольно невинно. Однако вы можете удивиться, узнав, что два fmt.Printf() выражения дают одинаковые результаты.

-> Before update [{Dog 4} {Chicken 2} {Snail 0}]  -> After update ??? [{Dog 4} {Chicken 2} {Snail 0}]

Подводный камень

Значения (хранятся как animal), по которым мы проходимся с помощью range, являются не указателями на значения, а копиями значений из zoo.

Решение

Чтобы изменить элемент массива, мы должны изменить этот элемент через указатель:

for idx, _ := range zoo {    zoo[idx].legs = 999  }

Возможно, это выглядит довольно тривиально, но вы можете быть удивлены, обнаружив, что это один из самых распространенных источников ошибок.

2. "…" и вариативные функции

Быть может, вы использовали “…” в ЯП С для создания вариативной функции; вариативная функция – это функция, принимающая переменное количество аргументов.

В C вы должны последовательно вызвать макрос va_arg для доступа к необязательным аргументам. И, если вы попытаетесь использовать вариативный аргумент любым другим способом, компилятор выдаст ошибку.

int add_em_up (int count,...) {    ...    va_start (ap, count);         /* Initialize the argument list */    for (i = 0; i < count; i++)        sum += va_arg(ap, int);   /* Get the next argument value */    va_end (ap);                  /* Clean up */    return sum  }

Программирование на Go задает несколько иные правила. В Golang это выглядит так же, как и в С, но работает по-другому. Здесь представлена вариативная функция myFprint. Обратите внимание, как используется вариативный аргумент a:

func myFprint(format string, a ...interface{}) {    if len(a) == 0 {      fmt.Printf(format)    } else {      // ? `a` should be `a...`      fmt.Printf(format, a)      // ?      fmt.Printf(format, a...)    }  }    func main() {      myFprint("%s : line %d ", "file.txt", 49)  }
[file.txt %!s(int=49)] : line %!d(MISSING)  file.txt : line 49

Можно подумать, что компилятор выдал бы ошибку при неправильном использовании вариативных параметров. Но обратите внимание, как fmt.Sprintf без нареканий использовал первый аргумент в a.

Подводный камень

В Go вариативные параметры разделяются компилятором.

Это означает, что вариативный аргумент a – на самом деле отдельная переменная. Поэтому приведённый ниже код действителен:

// `a` is just a slice!  for _, elem := range a {      fmt.Println(elem)  }

3. Слайсинг

Продолжим изучение Go слайсингом. Если вы делали слайсинг в Python, то наверняка помните, что этот приём даёт вам новый список со ссылками на элементы. Это свойство позволяет использовать такой Python код:

a = [1, 2, 3]  b = a[:2]			# ? a completely new list!  b[0] = 999  >>> a  [1, 2, 3]  >>> b  [999, 2]

Если вы попробуете то же самое в Go, получите что-то другое:

func main() {    data := []int{1,2,3}    slice := data[:2]    slice[0] = 999      fmt.Println(data)    fmt.Println(slice)  }
[999 2 3]  [999 2]

Подводный камень

В Go слайс имеет тот же базовый массив и ёмкость, что и оригинал. В Golang если вы измените элемент в слайсе, исходное содержимое тоже будет изменено.

Решение

Если вы хотите получить независимый слайс, у вас есть два варианта:

// Option #1  // appending elements to a nil slice  // `...` changes slice to arguments for the variadic function `append`  a := append([]int{}, data[:2]...)    // Option #1  // Create slice with length of 2  // copy(dest, src)  a := make([]int, 2)  copy(a, data[:2])

И, согласно StackOverflow, append намного быстрее make.

Материалы по теме:


Источник: proglib.io

Комментарии: