02. Declarations

Declarations

Имеется 4 вида объявлений в Go:

  • var для переменных

  • const для констант

  • func для функций

  • type для типов

Объявление в функции локальной переменной может затемнить объявление пакета. Если есть пакет bytes, то локальная переменная bytes перекроет его.

Variables

The var statement declares a list of variables; as in function argument lists, the type is last.

  • A var statement can be at package or function level.

  • A var declaration can include initialisers, one per variable.

  • If an initialiser is present, the type can be omitted; the variable will take the type of the initializer.

  • Если значение не указано, то значением переменной будет значение по умолчанию для своего типа. В Go не существует такого понятия как неинициализированная переменная.

  • In Go, some values are addressable (there is an address to find them). All variables are addressable and all constants are unaddressable.

var c, python, java bool
func main() {
    var i int
    fmt.Println(i, c, python, java) // 0 false false false
}
var i, j int = 1, 2

func main() {
    var python, java = true, false
    var c = "no!"
    var b, f, s = true, 3.14, "magic" // bool, float64, string
    fmt.Println(i, j, c, python, java) // 1 2 no! true false
}

There is also declaration list:

var (
    x int
    y =20
    z int=30
    d,e = 40, "hello" f, g string
)

Short variable declarations

Inside a function, the := short assignment statement can be used in place of a var declaration with implicit type. Outside a function, every statement begins with a keyword (var, func, and so on) and so the := construct is not available.

func main() {
    var i, j int = 1, 2
    k := 3
    c, python, java := true, false, "no!"

    fmt.Println(i, j, k, c, python, java) // 1 2 3 true false no!
}

Однако краткое объявление должно объявлять хоть одну новую переменную, остальные оно перезапишет.

in, err := os.Open(inFile)
out, err := os.Create(outfile) // перезапишет err

В локальных блоках, короткое объявление действует как объявление новой переменной и затемняет внешнюю переменную.

func shadow(a int) {
	if a == 0 {
		// Shadowing!
		a := 10
		fmt.Printf("a = %d\n", a) // 10
	}
	fmt.Printf("a = %d\n", a) // 0
}

shadow(0)

Because := only reuses variables that are declared in the current block. When using :=, make sure that you don’t have any variables from an outer scope on the lefthand side, unless you intend to shadow them.

Type inference

When declaring a variable without specifying an explicit type (either by using the := syntax or var = expression syntax), the variable's type is inferred from the value on the right hand side.

var i int
j := i // j is an int

Choosing declaration type

How do you know which style to use? As always, choose what makes your intent clearest.

  • The most common declaration style within functions is :=.

  • Outside of a function, use declaration lists on the rare occasions when you are declaring multiple package-level variables.

There are some situations within functions where you should avoid :=:

  • When initialising a variable to its zero value, use var x int. This makes it clear

    that the zero value is intended.

  • When assigning an untyped constant or a literal to a variable and the default type for the constant or literal isn’t the type you want for the variable, use the long var form with the type specified. While it is legal to use a type conversion to specify the type of the value and use := to write x := byte(20), it is idiomatic to write var x byte = 20.

  • Because := allows you to assign to both new and existing variables, it sometimes creates new variables when you think you are reusing existing ones. In those situations, explicitly declare all of your new variables with var to make it clear which variables are new, and then use the assignment operator (=) to assign values to both new and old variables.

You should rarely declare variables outside of functions, in what’s called the package block. Package-level variables whose values change are a bad idea. When you have a variable outside of a function, it can be difficult to track the changes made to it, which makes it hard to understand how data is flowing through your program. This can lead to subtle bugs. As a general rule, you should only declare variables in the package block that are effectively immutable.

Constants

Константы в Go имеют особую семантику:

  • Constants are declared like variables, but with the const keyword.

  • A const statement can appear anywhere a var statement can.

  • Constants can be character, string, boolean, or numeric values.

  • Constants in Go a the way to give a names to literals.

  • Constants cannot be declared using the := syntax.

  • Constants assigned at its declaration at compile-time.

  • Constants must be compile-time values const two = math.Sqrt(4) won't work.

  • Constants can be typed or untyped:

    • An untyped constant takes the type needed by its context.

    • A typed constant have explicit type.

  • Integer constants default to int, floating-point constants float64, rune constants to rune (an alias for int32), and imaginary constants to complex128.

  • Если имеется выражение из const, то тип результата зависит от типа последнего операнда в списке (что странно). 1 / 3 все равно даст 1, 1 / 2 тоже даст 1. То есть константы все же не ведут себя как числа.

  • In Go, some values are addressable (there is an address to find them). All variables are addressable and all constants are unaddressable. You can’t access it from its memory address. Unlike variables constants can't have pointers.

const Pi float64 = 3.14

// grouping declaration
const (
    Tau float64 = Pi * 2
    E float64 = 2.718
)

func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")

    const Truth = true
    fmt.Println("Go rules?", Truth)
}

При групповом определении констант работает авто-дополнение:

const (
	X float32 = 3.14
	Y           // here must be one identifier
	Z           // here must be one identifier

	A, B = "Go", "language"
	C, _
	// In the above line, the blank identifier
	// is required to be present.
)

// as =>

const (
	X float32 = 3.14
	Y float32 = 3.14
	Z float32 = 3.14

	A, B = "Go", "language"
	C, _ = "Go", "language"
)

Untyped constants

Untyped константа имеет значение, но не имеет стандартного типа из языка go. Она как и любая переменная хранится в памяти, и может быть использована, но у нее есть kind (разновидность), но не тип.

Kind константы определяется исходя из её значения. Значение константы называется идеальным значением (ideal value) и принадлежит к какому-то идеальному типу в математическом смысле (целые, вещественные, логические и т.д.). В зависимости от литерала константы Go преобразует это значение в какой-то из своих типов и назначает константе этот тип (default type):

Ideal type | Default type

  • booleanbool

  • runerune

  • integerint

  • floatingfloat64

  • stringstring

  • nil → none

Поэтому константы могут иметь значения которые не могут иметь переменные обычных типов Go.

Представление констант в памяти зависит от компилятора, но в целом они должны представляться одинаковыми значениями на разных платформах.

Вычисления над константами и литералами осуществляются во время компиляции, а не во время работы программы. Компилятор Go написан на Go. Нетипированные цифровые константы поддерживаются пакетом big, позволяя использовать все обычные операции с числами.

iota-expressions

iota starts constant numbering in Go. It doesn't mean “start from zero” as someone might expect. It is an index of a constant in the current const block:

const (
    myconst = "c"
    myconst2 = "c2"
    two = iota // 2
)

// Using iota twice doesn't reset the numbering:

const (
    zero = iota // 0
    one // 1
    two = iota // 2
)
  • iota изначально равна 0 и увеличивается на 1 для каждой следующей константы.

  • если для вычисления значения константы используется какое-то выражение в котором есть iota, то по умолчанию это выражение применяется и для последующих констант, но уже с увеличенным значением iota.

  • пустая переменная может использоваться для инкремента iota значения.

  • iota could be used to simulate C’s enum or #define constant.

  • The iota increments on the next line, rather than as soon as it gets referenced.

const (
    one = iota
    two
    _    // пустая переменная, пропуск iota
    four // = 4
)

Классический пример:

const (
    _         = iota // пропускаем первое значне
    KB uint64 = 1 << (10 * iota)
    MB
    GB
    TB
    PB
    EB
    // ошибка! переполнение типа
    // ZB
)

Custom Types

  • type выражение определяет новый тип.

  • type выражения можно использовать внутри функций

Auto-incrementing constants are often combined with a custom type, allowing you to lean on the compiler.

type Season uint8
const (
    Spring = Season(iota)
    Summer
    Autumn
    Winner
)

func (s Season) String() string {
    name := []string{"spring", "summer", "autumn", "winner"}
    i := uint8(s)
    switch {
    case i <= uint8(Winner):
        return name[i]
    default:
        return strconv.Itoa(int(i))
    }
}

func main() {
    s := Summer
    fmt.Println(s) // summer

    s = Season(9)
    fmt.Println(s) // 9
}

Enums

Here’s an idiomatic way to implement an enumerated type:

  • create a new integer type,

  • list its values using iota,

  • give the type a String function.

type Direction int

const (
    North Direction = iota
    East
    South
    West
)

var directionLabels = [...]string{"North", "East", "South", "West"}
func (d Direction) String() string {
    return directionLabels[d]
}

In use:

var d Direction = North
fmt.Println(d); // North
switch d {
case North:
    fmt.Println(" goes up.")
case South:
    fmt.Println(" goes down.")
default:
    fmt.Println(" stays put.")
}
// Output: North goes up.

Assignability

Assignability in Go

Having variable left of type T, there are 5 cases when value right can be assigned to left.

  1. Identical types

  2. Identical underlying types

  3. Assigning to interface type T value implementing T

  4. Assigning nil to variable which is a pointer, function, slice, map, channel or interface type.

  5. Untyped constants. Untyped constant can be set to variable of type T if constant is representable by value of type T.

var a float32
var b float64
var c int32
var d int64
const untyped = 1
a = untyped
b = untyped
c = untyped
d = untyped

Last updated