Especificación del lenguaje de programación GO

Versión del 20 de Marzo, 2015

Puedes ver el documento original aquí

Introducción

Este es el manual de referencia para el lenguaje de programación Go. Para más información y otros documentos, ve [golang.org][golang].

Go es un lenguaje de propósito general diseñado con la programación de sistemas en mente. Es fuertemente tipado, cuenta con su propio recolector de basura y apoyo explícito a la programación concurrente. Los programas se construyen a partir de paquetes, cuyas propiedades permiten una eficiente gestión de las dependencias. Las implementaciones existentes utilizan el tradicional modelo de compilación/enlace para generar los binarios ejecutables.

La gramática es compacta y regular, lo que facilita el análisis de las herramientas automáticas tal como los entornos de desarrollo integrado.

Notación

La sintaxis se especifica utilizando la Forma Backus-Naur Extendida (EBNF por Extended Backus-Naur Form):

Producción  = nombre_producción "=" [ Expresión ] "." .
Expresión   = Alternativa { "|" Alternativa } .
Alternativa = Término { Término } .
Término     = nombre_producción | símbolo [ "…" símbolo ] | Grupo | Opción
              | Repetición .
Grupo       = "(" Expresión ")" .
Opción      = "[" Expresión "]" .
Repetición  = "{" Expresión "}" .

Las producciones son expresiones construidas a partir de términos y los siguientes operadores, en orden incremental de precedencia:

|   alternancia
()  agrupación
[]  opción (0 o 1 veces)
{}  repetición (de 0 a n veces)

Los nombres de producción en minúsculas se usan para identificar símbolos léxicos. Los no terminales están en MayúsculasIntercaladas. Los símbolos léxicos se encierran entre comillas dobles "" o comillas inversas ``.

La forma a … b representa el conjunto de caracteres de a hasta b como alternativas. Los puntos suspensivos horizontales también se utilizan en otras partes de la especificación para denotar informalmente diversas enumeraciones o fragmentos de código que no se especifican más. El carácter (a diferencia de los tres caracteres ...) no es un símbolo del lenguaje Go.

Representación de código fuente

El código fuente es texto Unicode codificado en UTF-8. El texto no se canoniza, por lo que un único carácter acentuado es distinto del mismo carácter construido a partir de la combinación de un acento y una letra; estos son tratados como dos caracteres. Para simplificar, en este documento se utilizará el término no calificado carácter para referirse a un carácter Unicode en el texto fuente.

Cada carácter es distinto; por ejemplo, las letras mayúsculas y minúsculas son caracteres diferentes.

Restricción de implementación: Por compatibilidad con otras herramientas, un compilador puede rechazar el carácter NUL (U+0000) en el texto fuente.

Restricción de implementación: Por compatibilidad con otras herramientas, un compilador puede ignorar un byte de marca de orden (U+FEFF) codificado en UTF-8 si es el primer carácter Unicode en el texto fuente. Un byte de marca de orden se puede rechazar en cualquier otro lugar en la fuente.

Caracteres

Los siguientes términos se utilizan para referirse a clases específicas de caracteres Unicode:

nuevalínea         = /* el carácter Unicode U+000A */ .
carácter_unicode   = /* un carácter Unicode arbitrario excepto nuevalínea */ .
letra_unicode      = /* un carácter Unicode clasificado como "Letra" */ .
dígito_unicode     = /* un carácter Unicode clasificado como "Dígito decimal" */ .

El Estándar Unicode 7.0, en la Sección 4.5 "Categoría general" define un conjunto de categorías de caracteres. Go trata los caracteres en la categoría Lu, Ll, Lt, Lm o Lo como letras Unicode y los de la categoría Nd como dígitos Unicode.

Letras y dígitos

El carácter de subrayado _ (U+005F) se considera una letra.

letra           = letra_unicode | "_" .
dígito_decimal  = "0""9" .
dígito_octal    = "0""7" .
dígito_hex      = "0""9" | "A""F" | "a""f" .

Elementos léxicos

Comentarios

Hay dos formas de comentarios:

  1. Comentarios de una línea comienzan con la secuencia de caracteres // y terminan al final de la línea. Una línea de comentario actúa como una nuevalínea.
  2. Comentarios generales comienzan con la secuencia de caracteres /* y continúan hasta alcanzar la secuencia de caracteres */. Un comentario general que contiene uno o más saltos de línea actúa como un salto de línea, de lo contrario, actúa como un espacio.

Los comentarios no se anidan.

Símbolos

Los símbolos forman el vocabulario del lenguaje Go. Hay cuatro clases: identificadores, palabras clave, operadores/delimitadores y literales. El espacio en blanco, formado a partir de espacios (U+0020), tabuladores horizontales (U+0009), retornos de carro (U+000D) y saltos de línea (U+000A), se ignoran salvo los que separan a los símbolos que de otro modo se combinarían en un único símbolo. Además, un salto de línea o el final del archivo pueden desencadenar la inserción de un punto y coma. Aunque la entrada se divide en símbolos, el siguiente símbolo es la secuencia de caracteres más larga que forma un símbolo válido.

Puntos y comas

La gramática formal utiliza un punto y coma ";" como terminador en una serie de resultados. Los programas Go pueden omitir la mayoría de esos puntos y comas utilizando las dos siguientes reglas:

  1. Cuando la entrada se divide en símbolos, automáticamente se inserta un punto y coma en la cadena de componentes léxicos al final de una línea que no esté en blanco si el símbolo final de la línea es:

  2. Para permitir que las declaraciones complejas ocupen una sola línea, se puede omitir un punto y coma antes de un ")" o una "}" de cierre.

Para reflejar el uso idiomático, el código de los ejemplos de este documento omite el punto y coma utilizando estas reglas.

Identificadores

Los identificadores nombran entidades de programas tal como variables y tipos. Un identificador es una secuencia de una o más letras y dígitos. El primer carácter de un identificador debe ser una letra.

identificador = letra { letra | dígito_unicode } .
a
_x9
EstaVariableEsExportada
αβ

Algunos identificadores son predeclarados.

Palabras clave

Las siguientes palabras clave están reservadas y no se pueden utilizar como identificadores.

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

Operadores y delimitadores

Las siguientes secuencias de caracteres representan operadores, delimitadores y otros símbolos especiales:

+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^=

Enteros literales

Un entero literal es una secuencia de dígitos que representa una constante entera. Un prefijo opcional establece una base no decimal: 0 para octal, 0x o 0X para hexadecimal. En hexadecimales literales, las letras a-f y A-F representan valores del 10 al 15.

ent_lit     = decimal_lit | octal_lit | hex_lit .
decimal_lit = ( "1""9" ) { dígito_decimal } .
octal_lit   = "0" { dígito_octal } .
hex_lit     = "0" ( "x" | "X" ) dígito_hex { dígito_hex } .
42
0600
0xBadFace
170141183460469231731687303715884105727

Números de coma flotante literales

Un número de coma flotante literal es una representación decimal de una constante de coma flotante. Tiene una parte entera, un punto decimal, una parte fraccional y una parte exponente. Las partes entera y fraccional comprenden los dígitos decimales; la parte del exponente es una e o E seguida de un exponente decimal opcionalmente con signo. Se puede omitir la parte entera o la parte fraccional; o bien se puede omitir el punto decimal o el exponente.

comaflotante_lit = decimales "." [ decimales ] [ exponente ] |
                   exponente decimal |
                   "." decimales [ exponente ] .
decimales        = dígito_decimal { dígito_decimal } .
exponente        = ( "e" | "E" ) [ "+" | "-" ] decimales .
0.
72.40
072.40  // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5

Imaginarios literales

Un imaginario literal es una representación decimal de la parte imaginaria de una constante compleja. Consiste de un número de coma flotante literal o un entero decimal seguido de la letra i minúscula.

imaginario_lit = (decimales | comaflotante_lit) "i" .
0i
011i  // == 11i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i

Rune literales

Un rune literal representa una constante rune, un valor entero que identifica un carácter Unicode. Un rune literal se expresa como uno o más caracteres entre comillas simples. Dentro de las comillas, puede aparecer cualquier carácter excepto la comilla simple y el salto de línea. Un solo carácter entrecomillado representa el valor Unicode del carácter en sí mismo, mientras que las secuencias de varios caracteres que comienzan con una barra inversa codifican valores en varios formatos.

La forma más simple representa el único carácter dentro de las comillas; debido a que el texto fuente de Go son caracteres Unicode codificados en UTF-8, múltiples bytes codificados en UTF-8 pueden representar un único valor entero. Por ejemplo, la &#39;a&#39; literal contiene un solo byte que representa una a literal, el valor Unicode U+0061, el valor 0x61, mientras que &#39;ä&#39; tiene dos bytes (0xc3 0xa4) que representan una a-dieresis literal, U+00E4, el valor 0xe4.

Varias barras inversas permiten escapar valores arbitrarios para que sean codificados como texto ASCII. Hay cuatro formas de representar el valor entero como una constante numérica: \x seguida de exactamente dos dígitos hexadecimales; \u seguida de exactamente cuatro dígitos hexadecimales, \U seguida de exactamente ocho dígitos hexadecimales y una barra inversa \ seguida exactamente por tres dígitos octales. En cada caso el valor del literal es el valor representado por los dígitos en la base correspondiente.

Aunque todas estas representaciones resultan en un número entero, tienen diferentes rangos válidos. Los escapes octales deben representar un valor entre 0 y 255 inclusive. Los escapes hexadecimales satisfacen esta condición por su construcción. Los escapes \u y \U representan caracteres Unicode de modo que dentro de ellos algunos valores son ilegales, en particular los de arriba de 0x10FFFF y mitades sucedáneas.

Después de una barra inversa, algunos escapes de un solo carácter representan valores especiales:

\a   U+0007 alerta o campana
\b   U+0008 retroceso
\f   U+000C avance de página
\n   U+000A avance de línea o nueva línea
\r   U+000D retorno de carro
\t   U+0009 tabulación horizontal
\v   U+000b tabulación vertical
\\   U+005c barra inversa
\'   U+0027 comilla simple (escape válido solo dentro de runes literales)
\"   U+0022 comilla doble (escape válido solo dentro de cadenas literales)

Todas las demás secuencias que comienzan con una barra inversa son literales ilegales dentro de rune.

rune_lit           = "'" ( valor_unicode | valor_byte ) "'" .
valor_unicode      = carácter_unicode | valor_u_bajo | valor_u_alto | carácter_escapado .
valor_byte         = byte_valor_octal | byte_valor_hex .
byte_valor_octal   = `\` dígito_octal dígito_octal dígito_octal .
byte_valor_hex     = `\` "x" dígito_hex dígito_hex .
valor_u_bajo       = `\` "u" dígito_hex dígito_hex dígito_hex dígito_hex .
valor_u_alto       = `\` "U" dígito_hex dígito_hex dígito_hex dígito_hex
                           dígito_hex dígito_hex dígito_hex dígito_hex .
carácter_escapado  = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'aa'         // ilegal: demasiados caracteres
'\xa'        // ilegal: muy pocos dígitos hexadecimales
'\0'         // ilegal: muy pocos dígitos octales
'\uDFFF'     // ilegal: medio sustituto
'\U00110000' // ilegal: carácter Unicode no válido

Cadenas literales

Una cadena literal representa una cadena constante obtenida a partir de la concatenación de una secuencia de caracteres. Hay dos formas: cadenas literales crudas y cadenas literales interpretadas. Las cadenas literales crudas son secuencias de caracteres entre comillas inversas ``. Dentro de las comillas, es lícito cualquier carácter excepto la comilla inversa. El valor de una cadena de texto literal es la cadena compuesta por los caracteres no interpretados (codificados implícitamente en UTF-8) entre comillas; en particular, las barras inversas no tienen un significado especial y la cadena puede contener saltos de línea. Los caracteres de retorno de carro ('\r') dentro de las cadenas literales se descartan del valor de la cadena cruda.

Las cadenas literales interpretadas son secuencias de caracteres entre comillas dobles &quot;&quot;. El texto entre las comillas, que por cierto, no puede contener saltos de línea, forma el valor literal, con barras inversas escapa caracteres interpretados como son los rune literales (excepto que \&#39; es ilegal y \&quot; es legal), con las mismas restricciones. Los tres dígitos octales (\nnn) y dos dígitos hexadecimales (\xnn) representan bytes de escape individuales de la cadena resultante; todos los demás escapes representan la (posiblemente multi-byte) codificación UTF-8 de caracteres individuales. De este modo dentro de una cadena literal \377 y \xFF representan un solo byte de valor 0xFF=255, mientras ÿ, \u00FF, \U000000FF y \xc3\xbf representan los dos bytes 0xc3 y 0xbf de la codificación UTF-8 del carácter U+00FF.

cadena_lit               = cadena_lit_cruda | cadena_lit_interpretada .
cadena_lit_cruda         = "`" { carácter_unicode | nuevalínea } "`" .
cadena_lit_interpretada  = `"` { valor_unicode | valor_byte } `"` .
`abc`  // misma que "abc"
`\n
\n`    // misma que "\\n\n\\n"
"\n"
""
"¡Hola, mundo!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800"       // ilegal: medio sustituto
"\U00110000"   // ilegal: carácter Unicode no válido

Todos estos ejemplos representan la misma cadena:

"日本語"                                 // UTF-8 texto ingresado
`日本語`                                 // UTF-8 texto ingresado como un
                                        // literal crudo
"\u65e5\u672c\u8a9e"                    // los caracteres Unicode explicitos
"\U000065e5\U0000672c\U00008a9e"        // los caracteres Unicode explícitos
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"  // los bytes UTF-8 explícitos

Si el código fuente representa un carácter como dos caracteres, tal como implica una forma de combinar un acento y una letra, el resultado será un error si lo colocas en un rune literal (no es un único carácter) y aparecerá como dos caracteres si se coloca en una cadena literal.

Constantes

Hay constantes lógicas, constantes rune, constantes enteras, constantes de coma flotante, constantes complejas y constantes de cadenas de caracteres. Las constantes rune, enteras, de coma flotante y complejas se denominan colectivamente constantes numéricas.

Un valor constante está representado por un rune, número entero, de coma flotante, imaginario o cadena literal, un identificador denotando una constante, una expresión constante, una conversión con un resultado que es una constante o el valor resultante de alguna de las funciones integradas tal como unsafe.Sizeof aplicada a cualquier valor, cap o len aplicada a algunas expresiones, real e imag aplicada a una constante compleja y complex aplicada a constantes numéricas. Los valores lógicos de verdad están representados por las constantes predeclaradas true y false. El identificador predeclarado iota denota una constante entera.

En general, las constantes complejas son una forma de expresión constante y se explican en esa sección.

Las constantes numéricas representan valores de precisión arbitraria y no se desbordan.

Las constantes pueden ser tipadas o sin tipo. Las constantes literales, true, false, iota y ciertas expresiones constantes conteniendo solo operandos constantes sin tipo son sin tipo.

A una constante se le puede dar un tipo explícitamente por medio de una declaración de constante o conversión o implícitamente cuando se utiliza en una declaración de variable o una asignación o como un operando en una expresión. Es un error si el valor constante no se puede representar como un valor del tipo respectivo. Por ejemplo, a 3.0 se le puede dar cualquier tipo numérico tal como entero o de coma flotante, mientras que a 2147483648.0 (igual a 1&lt;&lt;31) se le pueden dar los tipos float32, float64 o Uint32 pero no int32 o string.

Una constante sin tipo tiene un tipo predeterminado, que es el tipo al que la constante se convierte implícitamente en contextos donde se requiere un valor tipado, por ejemplo, en una declaración corta de variable tal como i := 0, donde no hay ningún tipo explícito. El tipo predeterminado de una constante sin tipo es bool, rune, int, float64, complex128 o cadena, respectivamente, dependiendo de si se trata de un valor lógico, rune, número entero, de coma flotante, complejo o cadena constante.

No hay constantes que denoten los valores infinitos IEEE-754 ni not-a-number, pero las funciones Inf, NaN, IsInf e IsNaN del paquete math devuelven y prueban estos valores en tiempo de ejecución.

Restricción de implementación: A pesar de que en el lenguaje las constantes numéricas tienen precisión arbitraria, un compilador la puede implementar usando una representación interna con precisión limitada. Dicho esto, cada implementación debe:

  • Representar constantes enteras con al menos 256 bits.
  • Representar constantes de coma flotante, incluyendo las partes de una constante compleja, con una mantisa de al menos 256 bits y un exponente con signo de al menos 32 bits.
  • Dar un error si no puede representar con precisión una constante entera.
  • Dar un error si no puede representar una constante de coma flotante o compleja debido a desbordamiento.
  • Redondear a la constante representable más cercana si no puede representar una constante de coma flotante o compleja debido a los límites de precisión.

Estos requisitos se aplican tanto a las constantes literales como al resultado de la evaluación de expresiones constantes.

Variables

Una variable es una ubicación de almacenamiento para contener un valor. El conjunto de valores permisibles lo determina el tipo de la variable.

Una declaración de variable o, para los parámetros de función y resultados, la firma de una declaración de función o función literal reserva almacenamiento para una variable nombrada. Llamar a la función incorporada new o tomar la dirección de un literal compuesto reserva almacenamiento para una variable en tiempo de ejecución. Para referirse a tal variable anónima se usa una (posiblemente implícita) indirección de puntero.

Las variables estructuradas de tipo arreglo, sector y estructura tienen elementos y campos que se pueden encontrar individualmente. Cada uno de esos elemento actúa como una variable.

El tipo estático (o simplemente tipo) de una variable es el tipo determinado en su declaración, el tipo provisto en la llamada a new o al literal compuesto, o el tipo de un elemento de una variable estructurada. Las variables de tipo interfaz también tienen un tipo dinámico distinto, que es el tipo concreto del valor asignado a la variable en tiempo de ejecución (a menos que el valor sea el identificador predeclarado nil, mismo que no tiene tipo). El tipo dinámico puede variar durante la ejecución, pero los valores almacenados en variables interfaz siempre son asignables al tipo estático de la variable.

var x interface{}  // x es nil y tiene una interfaz de tipo estático
var v *T           // v tiene un valor nil, tipo estático *T
x = 42             // x tiene valor 42 y el tipo dinámico int
x = v              // x tiene un valor (*T)(nil) y el tipo dinámico *T

El valor de una variable se recupera haciendo referencia a la variable en una expresión; este es el valor más reciente asignado a la variable. Si una variable aún no tiene asignado un valor, su valor es el valor cero para su tipo.

Tipos

Un tipo determina el conjunto de valores y operaciones específicas para los valores de ese tipo. Los tipos podrán ser nombrados o anónimos. Los tipos con nombre se especifican mediante una (posiblemente cualificada) declaración type nombre; los tipos anónimos se especifican usando un tipo literal, que compone un nuevo tipo de los tipos existentes.

Tipo         = NombreTipo | TipoLit | "(" Tipo ")" .
NombreTipo   = identificador | IdentiCualificado .
TipoLit      = TipoArreglo | TipoEstructura | TipoPuntero | TipoFunción | TipoInterfaz |
               TipoSector | TipoMapa | TipoCanal .

Las instancias nombradas de tipos lógicos, numéricos y cadena son predeclaradas. Los tipos compuestos —arreglo, estructura, puntero, función, interfaz, sector, mapa y canal— se pueden construir mediante tipos literales.

Cada tipo T tiene un tipo subyacente: Si T es uno de los tipos predeclarados lógico, numérico, cadena o un tipo literal, el correspondiente tipo subyacente es T en sí mismo. De lo contrario, el tipo T subyacente es el tipo subyacente del tipo al que T se refiere en su declaración de tipo.

type T1 string
   type T2 T1
   type T3 []T1
   type T4 T3

El tipo subyacente de string, T1 y T2 es string. El tipo subyacente de []T1, T3 y T4 es []T1.

Conjunto de métodos

Un tipo puede tener un conjunto de métodos asociado a él. El conjunto de métodos de un tipo interfaz es su interfaz. El conjunto de métodos de cualquier otro tipo T se compone de todos los métodos declarados con el tipo receptor T. El conjunto de métodos del puntero al tipo correspondiente a *T es el conjunto de todos los métodos declarados con el receptor *T o T (es decir, que también contiene el conjunto de métodos de T). Las nuevas reglas se aplican a estructuras que contienen campos anónimos, como se describe en la sección sobre tipos estructura. Cualquier otro tipo tiene un conjunto de métodos vacío. En un conjunto de métodos, cada método debe tener un nombre de método único no blanco.

El conjunto de métodos de un tipo determina las interfaces que el tipo implementa y los métodos que se pueden llamar mediante un receptor de ese tipo.

Tipos lógicos

Un tipo lógico representa el conjunto de valores lógicos de verdad indicados por las constantes predeclaradas true y false. El tipo lógico predeclarado es bool.

Tipos numéricos

Un tipo numérico representa conjuntos de valores enteros o de coma flotante. Los tipos numéricos predeclarados independientes de la arquitectura son:

uint8       el conjunto de todos los enteros sin signo de 8 bits (0 a 255)
uint16      el conjunto de todos los enteros sin signo de 16 bits (0 a
                                                                    65535)
uint32      el conjunto de todos los enteros sin signo de 32 bits (0 a
                                                               4294967295)
uint64      el conjunto de todos los enteros sin signo de 64 bits
                                                  (0-18446744073709551615)

int8        el conjunto de todos los enteros con signo de 8 bits (-128 a
                                                                      127)
int16       el conjunto de todos los enteros con signo de 16 bits (-32768
                                                                  a 32767)
int32       el conjunto de todos los enteros con signo de 32 bits
                                                (-2147483648 a 2147483647)
int64       el conjunto de todos los enteros con signo de 64 bits
                              (-9223372036854775808 a 9223372036854775807)

float32     el conjunto de todos los números IEEE-754 de coma flotante de
                                                                  32 bits
float64     el conjunto de todos los números IEEE-754 de coma flotante de
                                                                  64 bits

complex64   el conjunto de todos los números complejos float32 con partes
                                                        real e imaginaria
complex128  el conjunto de todos los números complejos float64 con partes
                                                        real e imaginaria

byte        alias para uint8
rune        alias para int32

El valor de un entero n-bit es n bits de ancho y se representa usando aritmética de complemento a dos.

También hay una serie de tipos numéricos predeclarados con tamaños específicos de la implementación:

uint     entre 32 o 64 bits
int      mismo tamaño que uint
uintptr  un entero sin signo suficientemente grande para almacenar
         los bits no interpretados de un valor puntero

Para evitar problemas de portabilidad todos los tipos numéricos son distintos a excepción de byte, que es un alias para uint8 y rune, que es un alias para int32. Se necesita una conversión cuando diferentes tipos numéricos se mezclan en una expresión o asignación. Por ejemplo, int32 e int no son del mismo tipo a pesar de que pueden tener el mismo tamaño en una arquitectura particular.

Tipos cadena de caracteres (string)

Un tipo cadena representa el conjunto de valores de cadena de caracteres. Un valor de cadena es una (posiblemente vacía) secuencia de bytes. Las cadenas son inmutables: una vez creadas, no es posible cambiar el contenido de una cadena. El tipo predeclarado de cadena es string.

La longitud de una cadena s (su tamaño en bytes) se puede descubrir usando la función incorporada len. La longitud es una constante en tiempo de compilación si la cadena es una constante. Se puede acceder a los bytes de una cadena por índices enteros desde 0 hasta len(s)-1. Es ilegal tomar la dirección de tal elemento; si s[i] es el ienésimo byte de una cadena, &amp;s[i] no es válido.

Tipos arreglo

Un arreglo o vector es una secuencia numerada de elementos de un solo tipo, llamado el tipo del elemento. Al número de elementos se le conoce como longitud y nunca es negativo.

TipoArreglo      = "[" LongitudArreglo "]" TipoElemento .
LongitudArreglo  = Expresión .
TipoElemento     = Tipo .

La longitud es parte del tipo arreglo; se debe evaluar a una constante no negativa representable por un valor de tipo int. La longitud del arreglo a se puede descubrir usando la función incorporada len. Los elementos se pueden encontrar por índices enteros desde 0 hasta len(a)-1. Los tipos arreglo siempre son de una sola dimensión, pero pueden estar compuestos para formar tipos multidimensionales.

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // igual que [2]([2]([2]float64))

Tipos sector

Un sector es un descriptor de un segmento contiguo de un arreglo subyacente y proporciona acceso a una secuencia numerada de elementos de ese arreglo. Un tipo sector denota el conjunto de todos los sectores de arreglos del tipo de elemento. El valor de un sector sin iniciar es nil.

TipoSector = "[" "]" TipoElemento .

Al igual que los arreglos, los sectores son indexables y tienen una longitud. La longitud de un sector s se puede descubrir por medio de la función incorporada len; a diferencia de los arreglos puede cambiar durante la ejecución. Los elementos se pueden encontrar por medio de índices enteros desde 0 hasta len(s)-1. El índice de un elemento dado del sector puede ser menor que el índice del mismo elemento del arreglo subyacente.

Un sector, una vez iniciado, siempre está asociado con un arreglo subyacente que mantiene sus elementos. Por lo tanto, un sector comparte almacenamiento con su arreglo y con otros sectores del mismo arreglo; por el contrario, distintos arreglos siempre representan almacenamiento distinto.

El arreglo subyacente a un sector se puede extender más allá del final del sector. La capacidad es una medida de esa extensión: es la suma de la longitud del sector y la longitud del arreglo más allá del sector; puedes crear un sector de mayor longitud que la capacidad secccionando uno nuevo desde el sector original. La capacidad de un sector a se puede descubrir usando la función incorporada cap(a).

Un nuevo valor de sector iniciado, para un tipo de elemento T dado se crea usando la función integrada make, que toma un tipo sector y parámetros especificando la longitud y opcionalmente la capacidad. Un sector creado con make siempre asigna un nuevo arreglo oculto al cual el valor del sector devuelto refiere. Es decir, al ejecutar:

make([]T, longitud, capacidad)

produce el mismo sector como si asignaras un arreglo y después lo seccionaras, por lo tanto estas dos expresiones son equivalentes:

make([]int, 50, 100)
new([100]int)[0:50]

Como los arreglos, los sectores siempre son de una sola dimensión, pero pueden estar compuestos para construir objetos de mayores dimensiones. Con arreglos de arreglos, los arreglos internos siempre son, por construcción, de la misma longitud; sin embargo con los sectores de sectores (o arreglos de sectores), las longitudes internas pueden variar dinámicamente. Por otra parte, los sectores internos se deben iniciar individualmente.

Tipos estructura

Una estructura es una secuencia de elementos nombrados, llamados campos, cada uno de los cuales tiene un nombre y un tipo. Los nombres de campo se pueden especificar explícitamente (ListaIdentificador) o implícitamente (CampoAnónimo). En una estructura, no vacía los nombres de los campos deben ser únicos.

TipoEstructura   = "estructura" "{" { DeclaraCampo ";" } "}" .
DeclaraCampo     = ( ListaIdentificador Tipo | CampoAnónimo ) [ Etiqueta ] .
CampoAnónimo     = ["*"] NombreTipo .
Etiqueta         = cadena_lit .
// Una estructura vacía.
struct {}

// Una estructura con 6 campos.
struct {
    x, y int
    u float32
    _ float32  // relleno
    A *[]int
    F func()
}

Un campo declarado con un tipo pero no un nombre de campo explícito es un campo anónimo, también llamado campo incrustado o una incrustación del tipo en la estructura. Un tipo de campo se tiene que especificar como un nombre de tipo T o como un puntero a un nombre de tipo no interfaz *T y T en sí mismo no puede ser un tipo puntero. El nombre de tipo no cualificado actúa como el nombre del campo.

// Una estructura con cuatro campos anónimos de tipo T1, *T2,
// P.T3 y *P.T4
struct {
    T1       // el nombre del campo es T1
    *T2      // el nombre del campo es T2
    P.T3     // el nombre del campo es T3
    *P.T4    // el nombre del campo es T4
    x, y int // los nombres de campo son x e y
}

La siguiente declaración es ilegal ya que los nombres de campo deben ser únicos en un tipo estructura:

struct {
    T     // conflicto con los campos anónimos *T y *P.T
    *T    // conflicto con los campos anónimos T y *P.T
    *P.T  // conflicto con los campos anónimos T y *T
}

Un campo o método f de un campo anónimo en una estructura x se dice que fue promovido si x.f es un selector legal que denota ese campo o método f.

Los campos promovidos actúan como campos ordinarios de una estructura, excepto que no se pueden utilizar como nombres de campo en los literales compuestos de la estructura.

Dado un tipo estructura S y un tipo llamado T, los métodos promovidos se incluyen en el conjunto de métodos de la estructura de la siguiente manera:

  • Si S contiene un campo anónimo T, el conjunto de métodos de ambos S y *S incluyen métodos promovidos con receptor T. El conjunto de métodos de *S también incluye métodos promovidos con receptor *T.
  • Si S contiene un campo anónimo *T, ambos conjuntos de métodos de S y *S incluyen métodos promovidos con receptor T o *T.

Una declaración de campo puede estar seguida por una cadena literal opcional que hará las veces de etiqueta, que se convierte en un atributo para todos los campos en la declaración del campo correspondiente. Las etiquetas se hacen visibles a través de una interfaz de reflexión y toman parte en la identidad de tipo para estructuras pero de lo contrario se omiten.

// Una estructura que corresponde al protocolo TimeStamp con búfer.
// Las cadenas etiqueta definen el protocolo búfer para el número
// de campos.
struct {
    microsec    uint64 "campo 1"
    servidorIP6 uint64 "campo 2"
    proceso     string "campo 3"
}

Tipos puntero

Un tipo puntero denota el conjunto de todos los punteros a las variables de un determinado tipo, llamado el tipo base del puntero. El valor de un puntero no iniciado es nil.

TipoPuntero = "*" TipoBase .
TipoBase    = Tipo .
*Punto
*[4]int

Tipos función

Un tipo función indica el conjunto de todas las funciones con los mismos parámetros y tipo de resultados. El valor de una variable no iniciada de tipo función es nil.

TipoFunción        = "func" Firma .
Firma              = Parámetros [ Resultado ].
Resultado          = Parámetros | Tipo .
Parámetros         = "(" [ ListaDeParámetros [ "," ] ] ")".
ListaDeParámetros  = DeclaParámetro {"," DeclaParámetro }.
DeclaParámetro     = [ ListaIdentificador ] ["..."] Tipo .

Dentro de una lista de parámetros o resultados, los nombres (ListaIdentificador), deberían estar todos presentes o todos ausentes. Si están presentes, cada nombre se refiere a un elemento (parámetro o resultado) del tipo especificado y todos los nombres no vacíos de la firma deben ser únicos. Si esta ausente, cada tipo representa un elemento de ese tipo. Las listas de parámetros y resultados siempre están entre paréntesis excepto si hay exactamente un resultado anónimo que se pueda escribir como un tipo sin paréntesis.

El parámetro final en una firma de función puede tener un tipo prefijado con .... Una función con tal parámetro se llama variadica y se puede invocar con cero o más argumentos para ese parámetro.

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefijo string, valores ...int)
func(a, b int, z float64, opcional ...interface{}) (éxitoso bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

Tipos interfaz

Un tipo interfaz especifica un conjunto de métodos conocido como su interfaz. Una variable de tipo interfaz puede almacenar un valor de cualquier tipo con un conjunto de métodos que es algún superconjunto de la interfaz. Tal tipo se dice que implementa la interfaz. El valor de una variable no iniciada de tipo interfaz es nil.

TipoInterfaz         = "interface" "{" { EspecificaMétodo ";" } "}" .
EspecificaMétodo     = NombreMétodo  Firma | NombreTipoInterfaz .
NombreMétodo         = identificador .
NombreTipoInterfaz   = NombreTipo .

Al igual que con todos los conjuntos de métodos, en un tipo interfaz, cada método debe tener un nombre único no vacío.

// Una sencilla interfaz Documento
interface {
    Lee(b Buffer) bool
    Escribe(b Buffer) bool
    Cierra()
}

Más de un tipo puede implementar una interfaz. Por ejemplo, si dos tipos S1 y S2 tienen el conjunto de métodos:

func (p T) Lee(b Buffer) bool { return … }
func (p T) Escribe(b Buffer) bool { return … }
func (p T) Cierra() { … }

(Donde T representa ya sea a S1 o a S2), entonces la interfaz Documento la implementan tanto S1 como S2, independientemente de cuales otros métodos puedan tener o compartir S1 y S2.

Un tipo implementa cualquier interfaz que comprenda algún subconjunto de sus métodos y por lo tanto puede implementar varias interfaces distintas. Por ejemplo, todos los tipos implementan la interfaz vacía:

interface{}

Del mismo modo, considera esta especificación de interfaz que aparece dentro de una declaración de tipo para definir una interfaz llamada Archivero:

type Archivero interface {
    Bloquea()
    Desbloquea()
}

Si S1 y S2 también implementan

func (p T) Bloquea() { … }
func (p T) Desbloquea() { … }

implementan la interfaz Archivero así como la interfaz Documento.

Una interfaz T puede utilizar un (posiblemente cualificado) nombre de tipo interfaz E en lugar de una especificación de método. A esto se le conoce como incrustación de la interfaz E en T; esta añade todos los métodos (exportados y no exportados) de E a la interfaz T.

type LectorEscritor interface {
    Lee(b Buffer) bool
    Escribe(b Buffer) bool
}

type Documento interface {
    LectorEscritor  // igual que añadir los métodos de LectorEscritor
    Archivero       // igual que añadir los métodos de Archivero
    Cierra()
}

type DocumentoArchivado interface {
    Archivero
    Documento   // ilegal: Archivero, Desbloquea no es único
    Bloquea()   // ilegal: Bloquea no es único
}

Un tipo interfaz T no se puede incrustar a sí mismo o a cualquier tipo interfaz que incorpore T, recursivamente.

// ilegal: Mal no se puede incrustar
type Mal interface {
    Mal
}

// ilegal: Mal1 no se puede incrustar a sí misma usando Mal2
type Mal1 interface {
    Mal2
}

type Mal2 interface {
    Mal1
}

Tipos mapa

Un mapa es un grupo no ordenado de elementos de un tipo, conocido como el tipo del elemento, indexado por un conjunto de claves únicas de otro tipo, llamada la clave del tipo. El valor de un mapa sin iniciar es nil.

TipoMapa   = "map" "[" TipoClave "]" TipoElemento .
TipoClave  = Tipo .

Los operadores de comparación == y != deben estar completamente definidos como operandos del tipo clave; por lo que, el tipo clave no debe ser una función, mapa o sector. Si el tipo clave es un tipo interfaz, estos operadores de comparación los deben definir los valores de las claves dinámicas; un fallo provocará pánico en tiempo de ejecución.

map[string]int
map[*T]struct{ x, y float64 }
map[string]interfaz{}

El número de elementos del mapa se conoce como su longitud. En un mapa m su longitud se puede descubrir usando la función incorporada len y puede cambiar durante la ejecución. Puedes añadir elementos durante la ejecución utilizando asignaciones y se recuperan con expresiones índice; se pueden remover con la función incorporada delete.

Un nuevo valor de mapa vacío se crea usando la función integrada make, que toma el tipo mapa y un indicio de capacidad opcional como argumentos:

make(map[string]int)
make(map[string]int, 100)

La capacidad inicial no inmoviliza su tamaño: los mapas crecen para acomodar el número de elementos almacenados en ellos, a excepción de los mapas nil. Un mapa nil es equivalente a un mapa vacío salvo que no se le pueden añadir elementos.

Tipos canal

Un canal proporciona un mecanismo para ejecutar funciones concurrentemente para comunicarse enviando y recibiendo valores de un tipo de elemento especificado. El valor de un canal sin iniciar es nil.

TipoCanal = ("chan" | "chan" "<-" | "<-" "chan") TipoElemento .

El operador opcional &lt;- especifica la dirección del canal, enviar o recibir. Si no se da una dirección, el canal es bidireccional. Un canal puede estar limitado por conversión o asignación solo para enviar o solo para recibir.

chan T          // se puede utilizar para enviar y recibir valores
                // de tipo T
chan<- float64  // solo se puede utilizar para enviar float64
<-chan int      // solo se puede utilizar para recibir ints

El operador &lt;- posibilita la asociación con el canal de la izquierda:

chan<- chan int    // igual que chan<- (chan int)
chan<- <-chan int  // igual que chan<- (<-chan int)
<-chan <-chan int  // igual que <-chan (<-chan int)
chan (<-chan int)

Puedes crear un nuevo valor de canal iniciado usando la función integrada make, que toma como argumentos el tipo canal y una capacidad opcional:

make(chan int, 100)

La capacidad, en número de elementos, establece el tamaño del búfer en el canal. Si la capacidad es cero o está ausente, el canal es sin búfer y la comunicación tiene éxito solo cuando ambos, el remitente y el receptor están listos. De lo contrario, el canal se almacenará en un búfer y la comunicación será éxitosa sin bloqueo si el búfer no está lleno (envía) o no está vacío (recibe). Un canal nil nunca está listo para comunicación.

Un canal se puede cerrar con la función incorporada close. La forma de asignación de múltiples valores del operador receptor informa si un valor recibido fue enviado antes de que se cerrara el canal.

Un solo canal se puede utilizar en instrucciones de envío, operaciones de recepción y llamadas a las funciones incorporadas cap y len por una serie de rutinasgo sin más sincronización. Los canales actúan como colas PEPS (primero-en-entrar-primero-en-salir). Por ejemplo, si una rutinago envía los valores en un canal y una segunda rutinago los recibe, los valores se reciben en el orden enviado.

Propiedades de tipos y valores

Identidad de tipo

Dos tipos son o bien idénticos o diferentes.

Dos tipos nombrados son idénticos cuando sus nombres de tipo se originan en la misma EspeciTipo. Un tipo nombrado y uno anónimo siempre son diferentes. Dos tipos anónimos son idénticos si los tipos literales correspondientes son idénticos, es decir, si tienen la misma estructura literal y los componentes correspondientes tienen tipos idénticos. En detalle:

  • Dos tipos arreglo son idénticos si tienen elementos de tipos idénticos y los arreglos tiene la misma longitud.
  • Dos tipos sector son idénticos si el tipo de sus elementos es idéntico.
  • Dos tipos estructura son idénticos si tienen la misma secuencia de campos y si los campos correspondientes tienen los mismos nombres, tipos y etiquetas idénticas. Dos campos anónimos se considera que tienen el mismo nombre. Nombres de campo en minúsculas de diferentes paquetes siempre son diferentes.
  • Dos tipos puntero son idénticos si tienen tipos base idénticos.
  • Dos tipos función son idénticos si tienen el mismo número de parámetros y valores de resultado, los tipos de parámetros y resultados correspondientes son idénticos y, o bien ambas funciones son variadicas o ninguna de las dos. Los nombres de parámetros y resultados no es necesario que coincidan.
  • Dos tipos interfaz son idénticos si tienen el mismo conjunto de métodos con los mismos nombres y tipos función idénticos. Los nombres de método en minúsculas de diferentes paquetes siempre son diferentes. El orden de los métodos es irrelevante.
  • Dos tipos mapa son idénticos si tienen tipos de claves y valores idénticos.
  • Dos tipos canal son idénticos si tienen tipos de valores idénticos y la misma dirección.

Dadas las declaraciones:

type (
    T0 []string
    T1 []string
    T2 struct{ a, b int }
    T3 struct{ a, c int }
    T4 func(int, float64) *T0
    T5 func(x int, y float64) *[]string
)

estos tipos son idénticos:

T0 y T0
[]int e []int
struct{ a, b *T5 } y struct{ a, b *T5 }
func(x int, y float64) *[]string y func(int, float64) (resultado *[]string)

T0 y T1 son diferentes porque tienen nombres de tipo con declaraciones distintas; func(int, float64) *T0 y func(x int, y float64) *[]string son diferentes porque T0 es diferente de []string.

Asignabilidad

Un valor x es asignable a una variable de tipo T ("x es asignable a T") en cualquiera de estos casos:

  • el tipo x es idéntico a T.
  • los tipos x, V y T tienen idénticos tipos subyacentes y al menos uno de V o T no es un tipo nombrado.
  • T es un tipo interfaz y x implementa a T.
  • x es un valor de canal bidireccional, T es un tipo canal, los tipos x, V y T tienen tipos de elemento idénticos y al menos uno de V o T no es un tipo nombrado.
  • x es el identificador predeclarado nil y T es un tipo puntero, función, sector, mapa, canal o interfaz.
  • x es una constante sin tipo representable por un valor de tipo T.

Bloques

Un bloque es una secuencia posiblemente vacía de declaraciones e instrucciones entre llaves.

Bloque             = "{" ListaInstrucciones "}" .
ListaInstrucciones = { Declaración ";" } .

Además de los bloques explícitos en el código fuente, hay bloques implícitos:

  1. El bloque universal abarca todo el texto fuente Go.
  2. Cada paquete tiene un bloque de paquete que contiene todo el texto fuente Go para ese paquete.
  3. Cada archivo tiene un bloque de archivo que contiene todo el texto fuente Go en ese archivo.
  4. Cada declaración "if", "for" y "switch" se considera que están en su propio bloque implícito.
  5. Cada cláusula de una declaración "switch" o "select" actúa como un bloque implícito.

Bloques anidados e influencia y determinación de ámbito.

Declaraciones y ámbito

Una declaración vincula a un identificador no blanco con una constante, tipo, variable, función, etiqueta o paquete. Cada identificador en un programa se debe declarar. Ningún identificador se puede declarar dos veces en el mismo bloque y ningún identificador se puede declarar en ambos el bloque de archivo y el bloque de paquete.

El identificador blanco se puede utilizar como cualquier otro identificador en una declaración, pero no introduce un vínculo y por tanto no se ha declarado. En el bloque de paquete, el identificador init solo lo pueden utilizar las declaraciones de función init e igual que el identificador blanco este no introduce un nuevo vínculo.

Declaración        = DeclaConst | DeclaTipo | DeclaVar .
DeclaNivelSuperior = Declaración | DeclaFunción | DeclaMétodo .

El ámbito de un identificador declarado es la extensión del texto fuente en el que el identificador denota la constante, tipo, variable, función, etiqueta o paquete especificado.

Go tiene ámbito léxico utilizando bloques:

  1. El alcance de un identificador predeclarado es el bloque universal.
  2. El ámbito de un identificador que denota una constante, tipo, variable o función (pero no método) declarado en el nivel superior (fuera de cualquier función) es el bloque del paquete.
  3. El alcance del nombre del paquete de un paquete importado es el bloque de archivo del archivo que contiene la declaración de importación.
  4. El ámbito de un identificador que denota un método receptor, parámetro de función o variable de resultado es el cuerpo de la función.
  5. El ámbito del identificador de una constante o variable declarada dentro de una función se inicia al final de la EspeConst o EspeVar (DeclaVarCorta para declaraciones cortas de variables) y termina al final de la más interna que contiene el bloque.
  6. El alcance de un identificador de tipo declarado dentro de una función comienza en la EspeciTipo del identificador y termina al final del bloque más interno.

Un identificador declarado en un bloque se puede volver a declarar en un bloque interno. Mientras que el identificador de la declaración interna esté a su alcance, este se refiere a la entidad declarada por la declaración interna.

La cláusula package no es una declaración; el nombre del paquete no aparece en ningún ámbito. Su propósito es identificar los archivos pertenecientes a un mismo paquete y especificar el nombre del paquete predeterminado para las declaraciones de importación.

Ámbito de etiquetas

Las etiquetas son declaradas por las instrucciones etiquetadas y se utilizan en las instrucciones "break", "continue" y "goto". Es ilegal definir una etiqueta que nunca se utiliza. En contraste con otros identificadores, las etiquetas no se restringen a bloques y no entran en conflicto con los identificadores que no son etiquetas. El ámbito de una etiqueta es el cuerpo de la función en la que se declara y excluye el cuerpo de cualquier función anidada.

Identificador blanco

El identificador blanco está representado por el carácter de subrayado _. Sirve como un marcador de posición anónimo en lugar de un identificador regular (no blanco) y tiene un significado especial en declaraciones, como un operando y en las asignaciones.

Identificadores predeclarados

Los siguientes identificadores están declarados implícitamente en el bloque universal:

Tipos:
    bool byte error complex128 complex64 float32 float64
    int int8 int16 int32 int64 runa string
    uint uint8 uint16 uint32 uint64 uintptr

Constantes:
    true false iota

Valor cero:
    nil

Funciones:
    append cap close complex copy delete imag len
    make new panic print println real recover

Identificadores exportados

Un identificador se puede exportar para permitir el acceso al mismo desde otro paquete. Un identificador se exporta si se cumplen las siguientes condiciones:

  1. El primer carácter del nombre del identificador es una letra Unicode en mayúscula (Unicode clase "Lu"); y
  2. El identificador se declara en el bloque de paquete o se trata de un nombre de campo o un nombre de método.

Todos los otros identificadores no se exportan.

Unicidad de identificadores

Dado un conjunto de identificadores, un identificador se llama único si es diferente de todos los otros en el conjunto. Dos identificadores son diferentes si se escriben diferente o si aparecen en diferentes paquetes y no se exportan. De lo contrario, son los mismos.

Declaración de constantes

Una declaración de constante vincula una lista de identificadores (los nombres de las constantes) a los valores de una lista de expresiones constantes. El número de identificadores debe ser igual al número de expresiones y el enésimo identificador de la izquierda está unido al valor de la enésima expresión de la derecha.

DeclaConst         = "const" ( EspeConst | "(" { EspeConst ";" } ")" ) .
EspeConst          = ListaIdentificador [ [ Tipo ] "=" ListaExpresión ] .
ListaIdentificador = identificador { "," identificador } .
ListaExpresión     = Expresión { "," Expresión } .

Si el tipo está presente, todas las constantes toman el tipo especificado y las expresiones deben ser asignables a ese tipo. Si se omite el tipo, las constantes toman los tipos individuales de las expresiones correspondientes. Si los valores de la expresión sin tipo son constantes, las constantes declaradas permanecen sin tipo y los identificadores constantes denotan los valores constantes. Por ejemplo, si la expresión es un literal de coma flotante, el identificador constante denota una constante de coma flotante, incluso si la parte fraccionaria literal es cero.

const Pi float64 = 3.14159265358979323846
const cero = 0.0         // constante de coma flotante sin tipo
const (
    tamaño int64 = 1024
    fda          = -1    // constante entera sin tipo
)

const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", constantes
                             // enteras sin tipo y cadena
const u, v float32 = 0, 3    // u = 0.0, v = 3.0

Dentro de paréntesis la lista de declaración const y la lista de expresiones se puede omitir en cualquiera menos la primera declaración. Tal lista vacía es equivalente a la sustitución textual de la primer lista de expresiones no vacía anterior y su tipo si los hubiere. La omisión de la lista de expresiones es equivalente a la repetición de la lista anterior. El número de identificadores debe ser igual al número de expresiones en la lista anterior. Junto con el generator de constantes iota este mecanismo permite la declaración ligera de valores secuenciales:

const (
    Domingo      = iota
    Lunes
    Martes
    Miércoles
    Jueves
    Viernes
    DíaDelPartido
    númeroDeDías // esta constante no se exporta
)

Iota

Dentro de una declaración de constante, el identificador predeclarado iota representa constantes enteras sucesivas sin tipo. Se restablece a 0 cada vez que la palabra reservada const aparece en la fuente y se incrementa después de cada EspeConst. Se puede utilizar para construir un conjunto de constantes relacionadas:

const (  // iota se restablece a 0
    c0 = iota  // c0 == 0
    c1 = iota  // c1 == 1
    c2 = iota  // c2 == 2
)

const (
    a = 1 << iota  // a == 1 (iota se ha restablecido)
    b = 1 << iota  // b == 2
    c = 1 << iota  // c == 4
)

const (
    u         = iota * 42  // u == 0     (constante entera sin tipo)
    v float64 = iota * 42  // v == 42.0  (constante float64)
    w         = iota * 42  // w == 84    (constante entera sin tipo)
)

const x = iota  // x == 0 (iota se ha restablecido)
const y = iota  // y == 0 (iota se ha restablecido)

Dentro de una ListaExpresión, el valor de cada iota es el mismo, ya que solo se incrementa después de cada EspeConst:

const (
    bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0
    bit1, mask1                           // bit1 == 2, mask1 == 1
    _, _                                  // salta iota == 2
    bit3, mask3                           // bit3 == 8, mask3 == 7
)

Este último ejemplo explota la repetición implícita de la lista de expresiones no vacía anterior.

Declaración de tipos

Una declaración de tipo vincula un identificador, el nombre de tipo, a un nuevo tipo que tiene el mismo tipo subyacente que un tipo existente y las operaciones definidas para el tipo existente también están definidas para el nuevo tipo. El nuevo tipo es diferente del tipo existente.

DeclaTipo    = "type" ( EspeciTipo | "(" { EspeciTipo ";" } ")" ) .
EspeciTipo   = identificador de Tipo .
type ArregloEntero [16]int

type (
    Punto struct{ x, y float64 }
    Polar Punto
)

type ÁrbolDeNodo struct {
    izquierda, derecha *ÁrbolDeNodo
    valor *Comparable
}

type Bloque interface {
    TamañoBloque() int
    Cifra(fnt, dst []byte)
    Descifra(fnt, dst []byte)
}

El tipo declarado no hereda ningún método ligado al tipo existente, pero el conjunto de métodos de un tipo interfaz o de elementos de un tipo compuesto se mantiene sin cambios:

// Un Excluyente es un tipo de dato con dos métodos, Bloquea y Desbloquea.
type struct Excluyente { /* Campos de exclusión mutua (mutex) */ }

func (m *Excluyente) Bloquea() { /* Implementación de Bloquea */ }
func (m *Excluyente) Desbloquea() { /* Implementación de Desbloquea */ }

// NuevoExcluyente tiene la misma composición que un objeto de
// exclusión mutua pero su conjunto de métodos está vacío.
type NuevoExcluyente Excluyente

// El conjunto de métodos del tipo base de PuntExcluyente permanece sin
// cambios, pero el conjunto de métodos está vacío.
type PuntExcluyente *Excluyente

// El conjunto de métodos de *ExcluyenteImprimible contiene los métodos
// Bloquea y Desbloquea unido a su campo anónimo Excluyente.
type ExcluyenteImprimible struct {
    Excluyente
}

// MiBloque es un tipo interfaz que tiene el mismo conjunto
// de métodos que Bloque.
type MiBloque Bloque

Puedes utilizar una declaración de tipo para definir un diferente valor lógico, numérico o tipo cadena y conectarle métodos:

type ZonaHoraria int

const (
    EST ZonaHoraria = -(5 + iota)
    CST
    MST
    PST
)

func (zh ZonaHoraria) String() string {
    return fmt.Sprintf("GMT+%dh", zh)
}

Declaración de variables

Una declaración de variable crea una o más variables, vinculándolas a sus correspondientes identificadores, dándoles un tipo y valor inicial a cada una.

DeclaVar    = "var" ( EspeVar | "(" { EspeVar ";" } ")" ) .
EspeVar     = ListaIdentificador ( Tipo [ "=" ListaExpresión ] | "=" ListaExpresión ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2

var (
    i       int
    u, v, s = 2.0, 3.0, "bar"
)

var re, im = RaízCompleja(-1)
var _, encontrada = entradas[nombre]  // consulta mapa; solo está
                                      // interesado en "encontrada"

Si se da una lista de expresiones, las variables se inician con las expresiones siguiendo las reglas para asignaciones. De lo contrario, cada variable se inicia a su valor cero.

Si un tipo está presente, a cada variable se le da ese tipo. De lo contrario, a cada variable se le da el tipo del valor inicial correspondiente en la asignación. Si ese valor es una constante sin tipo, primero se convierte a su tipo predeterminado; si es un valor lógico sin tipo, primero se convierte al tipo bool. El valor predeclarado nil no se puede utilizar para iniciar una variable sin tipo explícito.

var d = math.Sin(0.5)  // d es float64
var i = 42             // i es int
var t, ok = x.(T)      // t es T, ok es bool
var n = nil            // ilegal

Restricción de implementación: Un compilador puede hacer que sea ilegal declarar una variable dentro del cuerpo de la función si la variable no se usa nunca.

Declaración corta de variables

Una declaración corta de variables utiliza la sintaxis:

DeclaVarCorta = ListaIdentificador ":=" ListaExpresión .

Esta es la forma abreviada de una declaración de variables con expresión iniciadora pero sin tipos:

"var" ListaIdentificador = ListaExpresión .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w := os.Pipe(fd)  // os.Pipe() devuelve dos valores
_, y, _ := coord(p)  // coord() devuelve tres valores; solo está
                     // interesado en la coordenada y

A diferencia de las declaraciones de variable regulares, una declaración corta de variable puede redeclarar variables siempre y cuando se hayan declarado originalmente antes en el mismo bloque y con el mismo tipo y por lo menos una de las variables no blanca sea nueva. Como consecuencia, una redeclaración solo puede aparecer en una declaración multivariable corta. La redeclaración no introduce una nueva variable; simplemente asigna un nuevo valor a la original.

campo1, desplazamiento := siguienteCampo(cadena, 0)
campo2, desplazamiento := siguienteCampo(str, desplazamiento) // redeclara
                                                              // desplazamiento
a, a := 1, 2           // ilegal: doble declaración de a o no nueva variable si
                       // a fue declarada en otra parte

Las declaraciones cortas de variable solo pueden aparecer en el interior de funciones. En algunos contextos, como en los iniciadores de las instrucciones "if", "for" o "switch" se pueden utilizar para declarar variables locales temporales.

Declaración de funciones

Una declaración de función vincula un identificador, el nombre de la función, a una función.

DeclaFunción  = "func" NombreFunción ( Función | Firma ) .
NombreFunción = identificador .
Función       = Firma CuerpoFunción .
CuerpoFunción = Bloque .

Si la firma de la función declara parámetros de resultado, la lista de instrucciones del cuerpo de la función debe terminar en una instrucción de terminación.

func encuentraMarcador(c <-chan int) int {
    for i := range c {
        if x := <-c; esMarcador(x) {
            return x
        }
    }
    // No válido: falta la instrucción de retorno.
}

Una declaración de función puede omitir el cuerpo. Tal declaración provee la firma para una función implementada fuera de Go, tal como una rutina de ensamblador.

func min(x int, y int) int {
    if x < y {
        return x
    }
    return y
}

func vacíaCaché(inicio, fin uintptr)  // implementada externamente

Declaración de métodos

Un método es una función con un receptor. Una declaración de método vincula a un identificador, el nombre del método, con un método y asocia el método con el tipo base del receptor.

DeclaMétodo  = "func" Receptor NombreMétodo ( Función | Firma ) .
Receptor     = Parámetros .

El receptor se especifica a través de una sección de parámetros extra precediendo al nombre del método. Esa sección de parámetros debe declarar un solo parámetro, el receptor. Su tipo debe ser de la forma T o *T (posiblemente usando paréntesis) donde T es un nombre de tipo. El tipo denotado por T se conoce como el tipo base del receptor; no debe ser un tipo puntero o interfaz y se debe declarar en el mismo paquete que el método. El método se dice que está unido al tipo base y el nombre del método es visible solo dentro de selectores para ese tipo.

Un identificador receptor no blanco debe ser único en la firma del método. Si no se hace referencia al valor del receptor dentro del cuerpo del método, su identificador se puede omitir en la declaración. Lo mismo se aplica en general a los parámetros de funciones y métodos.

Para un tipo base, los nombres no blancos de métodos vinculados a él deben ser únicos. Si el tipo base es un tipo estructura, el método y los nombres de campos no blancos deben ser distintos.

Dado el tipo Punto, las declaraciones

func (p *Punto) Longitud() float64 {
    return math.Sqrt(p.x * p.x + p.y * p.y)
}

func (p *Punto) Escala(factor float64) {
    p.x *= factor
    p.y *= factor
}

vincula a los métodos Longitud y Escala con el tipo receptor *Punto, de tipo base Punto.

El tipo de un método es el tipo de una función con el receptor como primer argumento. Por ejemplo, el método Escala tiene el tipo:

func(p *Punto, factor float64)

Sin embargo, una función declarada de esta manera no es un método.

Expresiones

Una expresión especifica el cálculo de un valor mediante la aplicación de operadores y funciones a operandos.

Operandos

Los operandos denotan los valores elementales de una expresión. Un operando puede ser un literal, un (posiblemente cualificado) identificador no blanco que denota una constante, variable o función, una expresión método produciendo una función o una expresión entre paréntesis.

El identificador blanco puede aparecer como un operando solo en el lado izquierdo de una asignación.

Operando       = Literal | NombreOperando | ExpreMétodo | "(" Expresión ")" .
Literal        = LitBásico | LitCompuesto | FunciónLit .
LitBásico      = ent_lit | float_lit | imaginario_lit | rune_lit | cadena_lit .
NombreOperando = identificador | IdentiCualificado.

Identificadores cualificados

Un identificador cualificado es un identificador prefijado con un nombre de paquete. Tanto el nombre del paquete como el identificador no deben estar en blanco.

IdentiCualificado = NombrePaquete "." identificador .

Un identificador cualificado accede a un identificador en un paquete diferente, que se debe importar. El identificador se debe exportar y estar declarado en el bloque de paquete de ese paquete.

math.Sin    // denota la función Sin en el paquete math

Literales compuestos

Los literales compuestos construyen valores para estructuras, arreglos, sectores, mapas y crean un nuevo valor cada vez que se evalúan. Estos consisten en el tipo del valor seguido de una lista encerrada entre llaves de elementos compuestos. Un elemento puede ser una sola expresión o un par clave-valor.

LitCompuesto    = TipoLiteral ValorLiteral .
TipoLiteral     = TipoEstructura | TipoArreglo | "[" "..." "]" TipoElemento |
                  TipoSector | TipoMapa | NombreTipo .
ValorLiteral    = "{" [ ListaElementos [ "," ] ] "}" .
ListaElementos  = Elemento { "," Elemento } .
Elemento        = [ Clave ":" ] Valor .
Clave           = NombreCampo | ÍndiceElemento .
NombreCampo     = identificador .
ÍndiceElemento  = Expresión .
Valor           = Expresión | ValorLiteral .

El TipoLiteral debe ser un tipo estructura, arreglo, sector o mapa (la gramática impone esta restricción, excepto cuando el tipo se da como un NombreTipo). Los tipos de las expresiones deben ser asignables al campo respectivo, elemento y tipo clave del TipoLiteral; no hay conversión adicional. La clave se interpreta como un nombre de campo para las estructuras literales, un índice de arreglo y sectores literales, y una clave para mapas literales. Para mapas literales, todos los elementos deben tener una clave. Es un error especificar múltiples elementos con el mismo nombre de campo o valor clave constante.

Para estructuras literales se aplican las siguientes reglas:

  • Una clave debe ser un nombre de campo declarado en el TipoLiteral.
  • Una lista de elementos que no contiene ninguna clave debe incluir un elemento para cada campo de estructura en el orden en que se declaran
    los campos.
    
  • Si algún elemento tiene una clave, cada elemento debe tener una clave.
  • Una lista de elementos que contiene claves no necesita tener un elemento para cada campo de la estructura. Los campos omitidos obtienen el
    valor cero para ese campo.
    
  • Un literal puede omitir la lista de elementos; dichos literales evalúan al valor cero para su tipo.
  • Es un error especificar un elemento de un campo no exportado de una estructura que pertenece a un paquete diferente.

Teniendo en cuenta las declaraciones

type Punto3D struct { x, y, z float64 }
type Línea struct { p, q Punto3D }

uno puede escribir

origen := Punto3D{}                              // valor cero para Punto3D
línea := Línea{origen, Punto3D{y: -4, z: 12.3}}  // valor cero para línea.q.x

Para los arreglos y sectores literales se aplican las siguientes reglas:

  • Cada elemento tiene un índice entero asociado marcando su posición en el arreglo.
  • Un elemento con una clave utiliza la clave como su índice; la clave debe ser una expresión constante entera.
  • Un elemento sin una clave usa el índice del elemento anterior más uno.
      Si el primer elemento no tiene clave, su índice es cero.
    

Tomar la dirección de un literal compuesto genera un puntero a una única variable iniciada con el valor del literal.

var puntero *Punto3D = &Punto3D{y: 1000}

La longitud de un arreglo literal es la longitud especificada en el TipoLiteral. Si en el literal se proporcionan menos elementos que la longitud, los elementos que faltan se establecen al valor cero para el tipo de elemento del arreglo. Es un error proporcionar elementos con valores de índice fuera del rango índice del arreglo. La notación ... especifica una longitud de arreglo igual al índice máximo de elementos más uno.

búfer  := [10]string{}               // len(búfer) == 10
setEnt := [6]int{1, 2, 3, 5}         // len(setEnt) == 6
días   := [...]string{"Sáb", "Dom"}  // len(días) == 2

Un sector literal describe todo el arreglo literal subyacente. Así, la longitud y capacidad de un sector literal son el índice del máximo elemento más uno. un sector literal tiene la forma

[]T{x1, x2, … xn}

y es la forma abreviada de una operación sector aplicada a un arreglo:

temp := [n]T{x1, x2, … xn}
temp[0 : n]

Dentro de un literal compuesto de arreglo, sector o mapa de tipo T, en que los elementos o claves de mapa en sí mismos son literales compuestos puedes omitir el tipo literal correspondiente si es idéntico al tipo de elemento o clave de mapa de T. Del mismo modo, los elementos que son direcciones de literales compuestos pueden omitir la &amp;T cuando el tipo del elemento o clave es *T.

Dentro de un literal compuesto de elementos arreglo, sector o mapa de tipo T, en que ellos mismos son literales compuestos puedes omitir el tipo literal correspondiente si es idéntico al tipo de elemento T. Del mismo modo, los elementos que son direcciones de literales compuestos pueden omitir la &amp;T cuando el tipo del elemento es *T.

[...]Punto{{1.5, -3.5}, {0, 0}}   // igual que [...]Punto{Punto{1.5, -3.5},
                                  //                      Punto{0, 0}}
[][]int{{1, 2, 3}, {4, 5}}        // igual que [][]int{[]int{1, 2, 3},
                                  //                   []int{4, 5}}
[][]Punto{{{0, 1}, {1, 2}}}       // igual que [][]Punto{[]Punto{Punto{0, 1},
                                  //                             Punto{1, 2}}}
map[string]Punto{"orig": {0, 0}}  // igual que map[string]Punto{"orig":
                                  //                              Punto{0, 0}}
[...]*Punto{{1.5, -3.5}, {0, 0}}  // igual que [...]*Punto{&Punto{1.5, -3.5},
                                  //                       &Punto{0, 0}}
map[Punto]string{{0, 0}: "orig"}  // igual que map[Punto]string{Punto{0, 0}:
                                  //                                   "orig"}

Surge un conflicto de ambigüedad cuando un literal compuesto usando la forma NombreTipo del TipoLiteral aparece como un operando entre la palabra clave y la llave de apertura del bloque de una declaración "if", "for" o "switch" y el literal compuesto no está entre paréntesis, corchetes o llaves. En este raro caso, la llave de apertura del literal es interpretado erróneamente como la introducción del bloque de instrucciones. Para resolver la ambigüedad, el literal compuesto debe aparecer entre paréntesis.

if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }

Ejemplos de literales válidos de arreglo, sector y mapa:

// Lista de números primos
primos := []int{2, 3, 5, 7, 9, 2147483647}

// vocales[v] es true si v es una vocal
vocales := [128]bool{'a': true, "e": true, "i": true, "o": true, "u": true, 'y': true}

// el arreglo [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filtro := [10]float32{-1, 4: -0.1, -0.1, 9: -1}

// Frecuencias en Hz de la escala temperada (A4 = 440Hz)
frecuenciaNota := map[string]float32{
    "C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
    "G0": 24.50, "A0": 27.50, "B0": 30.87,
}

Funciones literales

Un función literal representa una función anónima.

FunciónLit = "func" Función .
func(a, b int, z float64) bool { return a*b < int(z) }

Una función literal se puede asignar a una variable o invocarse directamente.

f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(canalRespuesta)

Las funciones literales son cierres: se pueden referir a variables definidas en funciones circundantes. Las variables se comparten entre las funciones circundantes y la función literal, además de que sobreviven siempre y cuando sean accesibles.

Expresiones primarias

Las expresiones primarias son los operandos de expresiones unarias y binarias.

ExprePrimaria =
    Operando |
    Conversión |
    ExprePrimaria Selector |
    ExprePrimaria Índice |
    ExprePrimaria Sector |
    ExprePrimaria TipoAserción |
    ExprePrimaria Argumentos .
Selector       = "." identificador .
Índice         = "[" Expresión "]" .
Sector         = "[" ( [ Expresión ] ":" [ Expresión ] ) |
                     ( [ Expresión ] ":" Expresión ":" Expresión )
                 "]" .
TipoAserción   = "." "(" Tipo ")" .

Argumentos     = "(" [ ( ListaExpresión | Tipo [ "," ListaExpresión ] )
                                                 [ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
Punto{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()

Selectores

Para una expresión primaria x que no es un nombre de paquete, la expresión selectora

x.f

denota el campo o método f del valor de x (o, a veces *x; véase más adelante). El identificador de f se conoce como el (campo o método) selector; no debe ser el identificador blanco. El tipo de la expresión selectora es el tipo de f. Si x es el nombre del paquete, consulta la sección sobre identificadores cualificados.

Un selector f puede denotar un campo o método f de un tipo T, o bien se puede referir a un campo o método f anidado en un campo anónimo de T. El número de campos anónimos atravesados para alcanzar f se conoce como su profundidad en T. La profundidad de un campo o método f declarada en T es cero. La profundidad de un campo o método f declarado en un campo anónimo A en T es la profundidad de f en A más uno.

Las siguientes reglas se aplican a los selectores:

  1. Para un valor x de tipo T o *T donde T no es un puntero o tipo interfaz, x.f denota el campo o método a la profundidad mínima en T donde hay tal f. Si no hay exactamente una f con menor profundidad, la expresión selectora es ilegal.
  2. Para un valor x de tipo I, donde I es un tipo interfaz, x.f denota el método real con el nombre f del valor dinámico de x. Si no hay ningún método con el nombre f en el conjunto de métodos de I, la expresión selectora es ilegal.
  3. Como excepción, si el tipo de x es un tipo puntero nombrado y (*x).f es una expresión selectora válida que denota un campo (pero no un método), x.f es la abreviatura de (*x).f.
  4. En todos los demás casos, x.f es ilegal.
  5. Si x es de tipo puntero y tiene el valor nil y x.f denota un campo de estructura, asignando o evaluando a x.f provoca pánico en tiempo de ejecución.
  6. Si x es de tipo interfaz y tiene el valor nil, llamar o evaluar el método x.f provoca pánico en tiempo de ejecución.

Por ejemplo, dadas las declaraciones:

type T0 struct {
    x int
}

func (*T0) M0()

type T1 struct {
    y int
}

func (T1) M1()

type T2 struct {
    z int
    T1
    *T0
}

func (*T2) M2()

type Q *T2

var t T2     // con t.T0 != nil
var p *T2    // con p != nil y (*p).T0 != nil
var q Q = p

uno puede escribir:

t.z          // t.z
t.y          // t.T1.y
t.x          // (*t.TO).x
p.z          // (*p).z
p.y          // (*p).T1.y
p.x          // (*(*p).T0).x
q.x          // (*(*q).T0).x        (*q).x es un campo selector válido
p.M2()       // p.M2()              M2 espera receptor de *T2
p.M1()       // ((*p).T1).M1()      M1 espera receptor de T1
p.M0()       // ((&(*p).T0)).M0()   M0 espera receptor de *T0, ve
             //                        la sección de Llamadas

pero lo siguiente no es válido:

q.M0()       // (*q).M0 es válido pero no un campo selector

Expresiones método

Si M está en el conjunto de métodos del tipo T, T.M es una función que se puede llamar como una función regular con los mismos argumentos que M prefijados por un argumento adicional que es el receptor del método.

ExpreMétodo   = TipoReceptor "." NombreMétodo .
TipoReceptor  = NombreTipo | "(" "*" NombreTipo ")" | "(" TipoReceptor ")" .

Considera un tipo estructura T con dos métodos, Mv, cuyo receptor es de tipo T y Mp, cuyo receptor es de tipo *T.

type T struct {
    a int
}

func (tv  T) Mv(a int) int         { return 0 }  // receptor del valor
func (tp *T) Mp(f float32) float32 { return 1 }  // puntero al receptor

var t T

La expresión

T.Mv

produce una función equivalente a Mv pero con un receptor explícito como primer argumento; el cuál tiene la firma

func(tv T, a int) int

Esa función se puede invocar normalmente con un receptor explícito, por lo que estas cinco llamadas son equivalentes:

t.Mv(7)
T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)

Del mismo modo, la expresión

(*T).Mp

produce un valor función representando a Mp con la firma

func(tp *T, f float32) float32

Para un método con un receptor de valor, se puede derivar una función con un puntero receptor explícito, por lo tanto

(*T).Mv

produce un valor función que representa a Mv con la firma

func(tv *T, a int) int

Tal función direcciona al receptor para crear un valor a pasar como el receptor para el método subyacente; el método no sobrescribe el valor cuya dirección se pasa en la llamada a la función.

El caso final, una función receptor-valor para un método receptor-puntero, es ilegal porque los métodos receptor-puntero no están en el conjunto de métodos del tipo valor.

Los valores función derivados de los métodos se invocan con la sintaxis de llamada a función; el receptor se proporciona como el primer argumento de la llamada. Es decir, dada f := T.Mv, f es invocada como f(t, 7) no t.f(7). Para construir una función que se vincule al receptor, usa una función literal o un valor método.

Es legal derivar un valor función desde un método de un tipo interfaz. La función resultante tiene un receptor explícito de ese tipo interfaz.

Valores método

Si la expresión x tiene tipo estático T y M está en el conjunto de métodos del tipo T, x.M se conoce como un valor método. El valor método x.M es un valor de función que es invocable con los mismos argumentos que una llamada al método x.M. La expresión x se evalúa y se guarda durante la evaluación del valor método; la copia guardada se utiliza entonces como el receptor en cualquier llamada que se pueda ejecutar después.

El tipo T puede ser un tipo interfaz o no interfaz.

Como en la explicación de la expresión método anterior, considera un tipo estructura T con dos métodos, Mv, cuyo receptor es de tipo T y Mp, cuyo receptor es de tipo *T.

type T struct {
    a int
}

func (tv  T) Mv(a int) int         { return 0 }  // receptor del valor
func (tp *T) Mp(f float32) float32 { return 1 }  // puntero al receptor

var t T
var pt *T

func makeT() T

La expresión

T.Mv

obtiene un valor de tipo función

func(int) int

Estas dos invocaciones son equivalentes:

t.Mv(7)
f := t.Mv; f(7)

Del mismo modo, la expresión

pt.Mp

obtiene un valor de tipo función

func(float32) float32

Como con los selectores, una referencia a un método no interfaz con un receptor de valor utilizando un puntero automáticamente desreferencia ese puntero: pt.Mv es equivalente a (*pt).Mv.

Al igual que con las llamadas a método, una referencia a un método no interfaz con un receptor puntero utilizando un valor direccionable automáticamente tomará la dirección de ese valor: t.Mp es equivalente a (&amp;t).Mp.

f := t.Mv; f(7)   // como t.Mv(7)
f := pt.Mp; f(7)  // como pt.Mp(7)
f := pt.Mv; f(7)  // como (*pt).Mv(7)
f := t.Mp; f(7)   // como (&t).Mp(7)
f := makeT().Mp   // no válido: el resultado de makeT() no es direccionable

Aunque los ejemplos anteriores utilizan tipos no interfaz, también es legal crear un valor método desde un valor de tipo interfaz.

var i interface { M(int) } = miValor
f := i.M; f(7)  // como i.M(7)

Expresiones índice

Una expresión primaria de la forma

a[x]

denota el elemento del arreglo, puntero a arreglo, sector, cadena o mapa a indexado por x. El valor x se conoce cómo el índice del arreglo o clave del mapa, respectivamente. Se aplican las siguientes reglas:

Si a no es un mapa:

  • el índice x debe ser de tipo entero o sin tipo; estar en el rango si 0 &lt;= x &lt; len(a), de lo contrario está fuera de rango
  • una constante índice no debe ser negativa y representable por un valor de tipo int

Para a de tipo arreglo A:

  • una constante índice debe estar en rango
  • si x está fuera de rango en tiempo de ejecución, se produce pánico en tiempo de ejecución
  • a[x] es el elemento del arreglo en el índice x y el tipo de a[x] es el tipo del elemento A

Para el puntero a al tipo arreglo:

  • a[x] es la abreviatura de (*a)[x]

Para a de tipo sector S:

  • si x está fuera de rango en tiempo de ejecución, se produce pánico en tiempo de ejecución
  • a[x] es el elemento del sector en el índice x y el tipo de a[x] es el tipo del elemento S

Para a de tipo cadena:

  • una constante índice debe estar en rango si la cadena a también es constante
  • si x está fuera de rango en tiempo de ejecución, se produce pánico en tiempo de ejecución
  • a[x] es el valor byte no constante en el índice x y el tipo de a[x] es byte
  • nada se puede asignar a a[x]

Para a de tipo mapa M:

  • el tipo de x debe ser asignable al tipo de la clave M
  • si el mapa contiene una entrada con clave x, a[x] es el valor del mapa con clave x y el tipo de a[x] es el valor del tipo M
  • si el mapa es nil o no contiene dicha entrada, a[x] es el valor cero para el valor del tipo M

De lo contrario a[x] es ilegal.

Una expresión índice en un mapa a de tipo map[K]V utilizado en una asignación o iniciación de la forma especial

v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]

adicionalmente arroja un valor lógico sin tipo. El valor de ok es true si la clave x está presente en el mapa y false de lo contrario.

Asignar a un elemento de un mapa nil provoca pánico en tiempo de ejecución.

Expresiones sector

Las expresiones sector construyen una subcadena o un sector a partir de una cadena, arreglo, puntero a arreglo o sector. Hay dos variantes: una forma simple que especifica una cota baja y alta, y una forma completa que también especifica un límite en capacidad.

Expresiones sector simples

Para una cadena, arreglo, puntero a arreglo o sector a, la expresión primaria

a[bajo : alto]

construye una subcadena o un sector. Los índices bajo y alto seleccionan los elementos del operando a que aparecen en el resultado. El resultado tiene índices que comienzan en 0 y una longitud igual a alto - bajo. Después de seccionar el arreglo a

a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]

el sector s tiene el tipo []int, longitud 3, capacidad 4 y los elementos:

s[0] == 2
s[1] == 3
s[2] == 4

Por conveniencia, cualquiera de los índices se puede omitir. A falta del índice bajo el valor predeterminado es cero; a falta del índice alto el predeterminado es la longitud del operando seccionado:

a[2:] // igual que a[2 : len(a)]
a[:3] // igual que a[0 : 3]
a[:]  // igual que a[0 : len(a)]

Si a es un puntero a un arreglo, a[bajo : alto] es la abreviatura de (*a)[bajo : alto].

Para arreglos o cadenas, los índices están en rango si 0 <= bajo <= alto <= len(a), de lo contrario estan fuera de rango. Para sectores, el índice del límite superior es la capacidad del sector cap(a) en lugar de la longitud. Una constante índice no debe ser negativa y representable por un valor de tipo int; para arreglos o cadenas constantes, las constantes índice también deben estar en rango. Si ambos índices son constantes, deben satisfacer bajo &lt;= alto. Si los índices están fuera de rango en tiempo de ejecución, se produce pánico en tiempo de ejecución.

Excepto para las cadenas sin tipo, si el operando seccionado es una cadena o un sector, el resultado de la operación de seccionado es un valor no constante del mismo tipo que el operando. Para operandos cadena sin tipo el resultado es un valor no constante de tipo cadena. Si el operando seccionado es un arreglo, debe ser direccionable y el resultado de la operación de seccionado es un sector con el mismo tipo de elemento que el arreglo.

Si el operando seccionado de una expresión sector válida es un sector nil, el resultado es un sector nil. De lo contrario, el resultado comparte su arreglo subyacente con el operando.

Expresiones sector complejas

Para un arreglo, puntero a arreglo o sector a (pero no una cadena), la expresión primaria

a[bajo : alto : máx]

construye un sector del mismo tipo y con la misma longitud y elementos que la simple expresión sector a[bajo : alto]. Además, esta controla la capacidad del sector resultante estableciéndola en máx - bajo. Únicamente se puede omitir el primer índice; su valor predeterminado es 0. Después de seccionar el arreglo a

a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]

el sector t tiene el tipo []int, longitud 2, capacidad 4 y los elementos

t[0] == 2
t[1] == 3

Cómo para las expresiones sector simples, si a es un puntero a un arreglo, a[bajo : alto : máx] es la abreviatura de (*a)[bajo : alto : máx]. Si el operando seccionado es un arreglo, debe ser direccionable.

Los índices están en rango si 0 &lt;= bajo &lt;= alto &lt;= máx &lt;= cap(a), si no están fuera de rango. Una constante índice no debe ser negativa y representable por un valor de tipo int; para arreglos, los índices constantes también deben estar en rango. Si varios índices son constantes, las constantes que están presentes deben estar en rango relativo de uno al otro. Si los índices están fuera de rango en tiempo de ejecución, se produce pánico en tiempo de ejecución.

Tipos aserción

Para una expresión x de tipo interfaz y un tipo T, la expresión primaria

x.(T)

afirma que x no es nil y que el valor almacenado en x es de tipo T. La notación x.(T) se llama tipo aserción.

Exactamente, si T no es un tipo interfaz, x.(T) afirma que el tipo dinámico de x es idéntico al tipo de T. En este caso, T debe implementar la (interfaz) del tipo x; de lo contrario el tipo aserción no es válido ya que no es posible para x almacenar un valor de tipo T. Si T es un tipo interfaz, x.(T) afirma que el tipo dinámico de x implementa la interfaz T.

Si el tipo aserción se mantiene, el valor de la expresión es el valor almacenado en x y su tipo es T. Si el tipo aserción es falso, se produce pánico en tiempo de ejecución. En otras palabras, aunque el tipo dinámico de x es conocido solo en tiempo de ejecución, el tipo de x.(T) es conocido por ser T en un programa correcto.

var x interface{} = 7  // x tiene tipo dinámico int y valor 7

i := x.(int)           // i tiene tipo int y valor 7.

type I interface { m() }

var y I

s := y.(string)        // ilegal: "string" no implementa a I (falta el
                       // método m)

r := y.(io.Reader)     // r tiene tipo io.Reader e y debe implementar
                       // ambas I e io.Reader

Un tipo aserción usado en una asignación o iniciación de la forma especial

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)

adicionalmente arroja un valor lógico sin tipo. El valor de ok es true si se mantiene la aserción. De lo contrario, es false y el valor de v es el valor cero para el tipo T. En este caso no ocurre ningún pánico en tiempo de ejecución.

Llamadas

Dada una expresión f de tipo función F:

f(a1, a2, … an)

invoca a f con argumentos a1, a2, … an. A excepción de un caso especial, los argumentos deben ser expresiones de un solo valor asignable a los tipos de parámetros de F y son evaluados antes de llamar a la función. El tipo de la expresión es el tipo de resultado de F. Una invocación a método es similar, pero el método en sí se especifica como un selector sobre un valor del tipo receptor para el método.

math.Atan2(x, y)  // invoca a la función

var pt *Punto

pt.Scale(3.5)     // llamada a método con pt como receptor

En una llamada a función, el valor de la función y los argumentos se evalúan en el orden habitual. Después de evaluarlos, los parámetros de la llamada se pasan por valor a la función y la función llamada comienza su ejecución. Los parámetros de retorno de la función de nuevo se pasan por valor a la función llamada cuando la función regresa.

Llamar a un valor de función nil provoca pánico en tiempo de ejecución.

Como caso especial, si los valores de retorno de una función o método g son iguales en número e individualmente asignables a los parámetros de otra función o método f, entonces la llamada f(g(parámetros-de-g)) invocará a f después de vincular en orden los valores de retorno de g a los parámetros de f. La llamada a f no debe contener ningún otro parámetro salvo la llamada a g y g debe tener por lo menos un valor de retorno. Si f tiene un parámetro ... final, este se asigna el valor de retorno de g que permanece después de la asignación de los parámetros normales.

func Divide(s string, pos int) (string, string) {
    return s[0:pos], s[pos:]
}

func Junta(s, t string) string {
    return s + t
}

if Junta(Divide(valor, len(valor)/2)) != valor {
    log.Panic("prueba fallida")
}

Una llamada al método x.m() es válida si el conjunto de métodos de (el tipo de) x contiene m y la lista de argumentos se puede asignar a la lista de parámetros de m. Si x es direccionable y el conjunto de métodos de &amp;x contiene a m, x.m() es una abreviatura de (&amp;x).m():

var p Punto
p.Escala(3.5)

No hay ningún tipo método distinto y no hay métodos literales.

Pasando argumentos a parámetros ...

Si f es variadica con un parámetro final p de tipo ...T, entonces dentro de f de tipo p es equivalente al tipo []T. Si f se invoca sin argumentos reales de p, el valor pasado a f es nil. De lo contrario, el valor pasado es un nuevo sector de tipo []T con un nuevo arreglo subyacente cuyos elementos sucesivos son los argumentos reales, todos ellos deben ser asignables a T. Por tanto, la longitud y capacidad del sector es el número de argumentos ligados a p y pueden ser diferentes para cada sitio de la llamada.

Dada la función y llamadas

func Saluda(prefijo string, quién ...string)

Saluda("nadie")
Saluda("hola", "José", "Ana", "Elena")

dentro de Saluda, quién tendrá el valor nil en la primera llamada y []string{&quot;José&quot;, &quot;Ana&quot;, &quot;Elena&quot;} en la segunda.

Si el argumento final es asignable a un tipo sector []T, se puede pasar sin cambios como el valor de un parámetro ...T si el argumento está seguido por .... En este caso no se crea un nuevo sector.

Dado el sector s y la llamada

s := []string{"Jaime", "Jazmín"}
Saluda("adiós:", s...)

dentro de Saluda, quién tendrá el mismo valor que s con el mismo arreglo subyacente.

Operadores

Los operadores combinan operandos en expresiones.

Expresión   = ExprUnaria | Expresión op_binario ExprUnaria .
ExprUnaria  = ExprePrimaria | op_unario ExprUnaria .
op_binario  = "||" | "&&" | op_rel | op_adi | op_mul .
op_rel      = "==" | "!=" | "<" | "<=" | ">" | ">=" .
op_adi      = "+" | "-" | "|" | "^" .
op_mul      = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .
op_unario   = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

Las comparaciones se dilucidan en otro lugar. Para otros operadores binarios, los tipos de los operandos deben ser idénticos a menos que la operación involucre desplazamientos o constantes sin tipo. Para las operaciones que solo implican constantes, consulta la sección sobre expresiones constantes.

Excepto para las operaciones de desplazamiento, si un operando es una constante sin tipo y el otro operando no, la constante se convierte al tipo del otro operando.

El operando de la derecha en una expresión de desplazamiento debe tener tipo entero sin signo o ser una constante sin tipo que se pueda convertir al tipo entero sin signo. Si el operando de la izquierda de una expresión de desplazamiento no constante es una constante sin tipo, el tipo de la constante es lo que sería si la expresión de desplzamiento fuera reemplazada solo por su operando izquierdo.

var s uint = 33
var i = 1<<s           // 1 tiene tipo int
var j int32 = 1<<s     // 1 tiene tipo int32; j == 0
var k = uint64(1<<s)   // 1 tiene tipo uint64; k == 1<<33
var m int = 1.0<<s     // 1.0 tiene tipo int
var n = 1.0<<s != i    // 1.0 tiene tipo int; n == false si los enteros
                       // son de 32 bits
var o = 1<<s == 2<<s   // 1 y 2 tiene tipo int; o == true si los enteros
                       // son de 32 bits
var p = 1<<s == 1<<33  // ilegal si los enteros son de 32 bits: 1 tiene
                       // tipo int, pero 1<<33 desborda el int
var u = 1.0<<s         // ilegal: 1.0 tiene tipo float64, no se puede
                       // desplazar
var u1 = 1.0<<s != 0   // ilegal: 1.0 tiene tipo float64, no se puede
                       // desplazar
var u2 = 1<<s != 1.0   // ilegal: 1.0 tiene tipo float64, no se puede
                       // desplazar
var v float32 = 1<<s   // ilegal: 1 tiene tipo float64, no se puede
                       // desplazar
var w int64 = 1.0<<33  // 1.0<<33 es una expresión de desplazamiento
                       // constante

Precedencia de operadores

Los operadores unarios tienen la precedencia más alta. Puesto que ++ y -- forman declaraciones de operadores, no expresiones, caen fuera de la jerarquía del operador. Por consecuencia, la declaración *p++ es la misma que (*p)++.

Hay cinco niveles de precedencia en los operadores binarios. Los operadores de multiplicación se vinculan fuertemente, seguidos de los operadores de adición, operadores de comparación &amp;&amp; (AND lógico) y finalmente || (OR lógico):

Precedencia              Operador
    5              *  /  %  <<  >>  &  &^
    4              +  -  |  ^
    3              ==  !=  <  <=  >  >=
    2              &&
    1              ||

Los operadores binarios de la misma precedencia se asocian de izquierda a derecha. Por ejemplo, x / y * z es lo mismo que (x / y) * z.

+x
23 + 3*x[i]
x <= f()
^a >> b
f() || g()
x == y+1 && <-chanPtr > 0

Operadores aritméticos

Los operadores aritméticos se aplican a los valores numéricos y producen un resultado del mismo tipo que el primer operando. Los cuatro operadores aritméticos estándar (+, -, * y /) se aplican a tipos numéricos enteros, de coma flotante y complejos; + también se aplica a cadenas de caracteres. Todos los demás operadores aritméticos solo se aplican a enteros.

+    suma              valores numéricos enteros, de coma flotante,
                                                       complejos y cadenas
-    diferencia        valores numéricos enteros, de coma flotante,
                                                                 complejos
*    producto          valores numéricos enteros, de coma flotante,
                                                                 complejos
/    cociente          valores numéricos enteros, de coma flotante,
                                                                 complejos
%    residuo           enteros

&    AND a nivel de bits        enteros
|    OR a nivel de bits         enteros
^    XOR a nivel de bits        enteros
&^   limpieza de bit (y NOT)    enteros
<<   desplaza a la izquierda    entero << entero sin signo
>>   desplaza a la derecha      entero >> entero sin signo

Las cadenas se pueden concatenar usando el operador + o el operador de asignación +=:

s := "hola" + string(c)
s += " y adiós"

La adición de cadenas crea una nueva cadena concatenando los operandos.

Para dos valores enteros x e y, el cociente entero q = x / y y residuo r = x % y satisfacen las siguientes relaciones:

x = q*y + r  and  |r| < |y|

con x / y truncado hacia cero ("división truncada").

x     y     x / y     x % y
 5     3       1         2
-5     3      -1        -2
 5    -3      -1         2
-5    -3       1        -2

Como una excepción a esta regla, si el dividendo x es el valor negativo para el tipo int de x, el cociente q = x / -1 es igual a x (y r = 0).

x, q
int8                     -128
int16                  -32768
int32             -2147483648
int64    -9223372036854775808

Si el divisor es una constante, esta no debe ser cero. Si el divisor es cero en tiempo de ejecución, se produce pánico en tiempo de ejecución. Si el dividendo no es negativo y el divisor es una constante potencia de 2, la división se puede reemplazar por un desplazamiento a la derecha y el cálculo del residuo se podrá sustituir por una operación AND bit a bit:

x     x / 4     x % 4     x >> 2     x & 3
 11      2         3         2          3
-11     -2        -3        -3          1

Los operadores de desplazamiento desplazan el operando de la izquierda por el valor de desplazamiento especificado por el operando de la derecha. Estos implementan desplazamiento aritmético si el operando de la izquierda es un entero con signo y los desplazamientos lógicos si se trata de un entero sin signo. No hay un límite superior para el valor del desplazamiento. Los desplazamientos se comportan como si el operando de la izquierda se desplazara n veces por 1 para un valor de desplazamiento n. Como resultado, x &lt;&lt; 1 es lo mismo que x*2 y x &gt;&gt; 1 es lo mismo que x/2, pero truncado hacia el infinito negativo.

Para operandos enteros, los operadores unarios +, - y ^ se definen de la siguiente manera:

+x                            es 0 + x
-x    negación                es 0 - x
^x    complemento bit a bit   es m ^ x  dónde m = "todos los bits puestos a 1"
                                                  para x sin signo
                                           y  m = -1 para x con signo

Para números de coma flotante y complejos, +x es lo mismo que x, mientras que -x es la negación de x. El resultado de una división de coma flotante o compleja entre cero no se especifica más allá del estándar IEEE-754; cuando se produce pánico en tiempo de ejecución es específico de la implementación.

Desbordamiento de enteros

Para valores enteros sin signo, las operaciones +, -, * y &lt;&lt; se calculan módulo 2 n , donde n es el número de bits del tipo entero sin signo. En términos generales, estas operaciones de enteros sin signo descartan bits altos sobre desbordamiento y los programas pueden depender de ``aproximaciones''.

Para enteros con signo, las operaciones +, -, * y &lt;&lt; legalmente pueden desbordar y si el valor resultante existe y está definido deterministamente por la representación del entero con signo, la operación y sus operandos. No se lanza una excepción como resultado de desbordamiento. Un compilador no puede optimizar el código bajo el supuesto de que no se produce desbordamiento. Por ejemplo, no puede suponer que x &lt; x + 1 siempre sea cierto.

Operadores de comparación

Los operadores de comparación comparan dos operandos y producen un valor lógico sin tipo.

==    igual
!=    no igual
<     menor
<=    menor o igual
>     mayor
>=    mayor o igual

En cualquier comparación, el primer operando debe ser asignable al tipo del segundo operando o viceversa.

Los operadores de igualdad == y != se aplican a los operandos que son comparables. Los operadores de ordenamiento &lt;, &lt;=, &gt; y &gt;= se aplican a los operandos que son ordenados. Estos términos y el resultado de las comparaciones se definen de la siguiente manera:

  • Los valores lógicos son comparables. Dos valores lógicos son iguales si ambos son true o ambos son false.
  • Los valores enteros son comparables y ordenados, de la forma habitual.
  • Los valores de coma flotante son comparables y ordenados, como lo define la norma IEEE-754.
  • Los valores complejos son comparables. Dos valores complejos u y v son iguales si ambos son real(u) == real(v) y imag(u) == imag(v).
  • Los valores de cadena son comparables y ordenados, léxicamente bit a bit.
  • Los valores puntero son comparables. Dos valores puntero son iguales si apuntan a la misma variable o si ambos tienen valor nil. Los punteros a distinta variable de tamaño cero pueden o no ser iguales.
  • Los valores de canal son comparables. Dos valores de canal son iguales si fueron creados por la misma llamada a make o si ambos tienen valor nil.
  • Los valores de interfaz son comparables. Dos valores de interfaz son iguales si tienen idénticos tipos dinámicos y valores dinámicos iguales o si ambos tienen valor nil.
  • Un valor x de tipo no interfaz X y un valor t de tipo interfaz T son comparables cuando los valores del tipo X son comparables y X implementa a T. Ellos son iguales si el tipo dinámico de t es idéntico a X y el valor dinámico de t es igual a x.
  • Los valores estructura son comparables si todos sus campos son comparables. Dos valores estructura son iguales si sus correspondientes campos no blancos son iguales.
  • Los valores de arreglo son comparables si el tipo de los valores de los elementos del arreglo son comparables. Dos valores de arreglo son iguales si sus correspondientes elementos son iguales.

Una comparación de dos valores interfaz con tipos dinámicos idénticos provoca pánico en tiempo de ejecución si los valores de ese tipo no son comparables. Este comportamiento se aplica no solo a las comparaciones de valores interfaz directos sino también al comparar arreglos de valores interfaz o estructuras con campos de valor interfaz.

Los valores de sector, mapa y función no son comparables. Sin embargo, como un caso especial, un valor de sector, mapa o función se puede comparar con el identificador predeclarado nil. La comparación de valores puntero, canal e interfaz a nil también se permite y sigue las reglas generales anteriores.

const c = 3 < 4            // c es true, la constante lógica sin tipo

type MiBooleano bool

var x, y int

var (
    // el resultado de una comparación es un bool sin tipo.
    // Aplican las reglas de asignación habituales.
    b3        = x == y     // b3 es de tipo bool
    b4 bool   = x == y     // b4 es de tipo bool
    b5 MiBooleano = x == y // b5 es de tipo MiBooleano
)

Operadores lógicos

Los operadores lógicos se aplican a valores booleanos y producen un resultado del mismo tipo que los operandos. El operando de la derecha se evalúa de forma condicional.

&&    AND condicional    p && q  es  "si p entonces q si no false"
||    OR condicional     p || q  es  "si p entonces true si no q"
!     NOT                !p      es  "no p"

Operadores de dirección

Para un operando x de tipo T, la operación de dirección &amp;x genera un puntero de tipo *T a x. El operando debe ser direccionable, es decir, ya sea una variable, indirección de puntero o la operación de indexación de un sector; o un selector de campo de un operando direccionable a estructura; o una operación de indexación de arreglo de un arreglo direccionable. Como excepción al requisito de direccionamiento, x también puede ser un (posiblemente encerrado entre paréntesis) literal compuesto. Si la evaluación de x pudiera provocar pánico en tiempo de ejecución, la evaluación de x también lo haría.

Para un operando x de tipo puntero *T, la indirección de puntero *x denota la variable de tipo T apuntada por x. Si x es nil, un intento de evaluar *x provocará pánico en tiempo de ejecución.

&x
&a[f(2)]
&Punto{2, 3}
*p
*pf(x)
var x *int = nil
*x   // provoca pánico en tiempo de ejecución
&*x  // provoca pánico en tiempo de ejecución

Operador de recepción

Para un operando ch de tipo canal, el valor de la operación de recepción &lt;-ch es el valor recibido desde el canal ch. La dirección del canal debe permitir operaciones de recepción y el tipo de la operación de recepción es el tipo de elemento del canal. Los bloques de expresión hasta un cierto valor están disponibles. Recibir desde un canal nil lo bloquea por siempre. Una operación de recepción en un canal cerrado siempre se puede proceder inmediatamente, dando el tipo de elemento de valor cero después de que se hayan recibido todos los valores previamente enviados.

v1 := <-ch
v2 = <-ch
f(<-ch)
<-estrobo  // Espera hasta un pulso del reloj
           // y desecha el valor recibido

Una expresión de recepción utilizada en una asignación o iniciación de la forma especial

x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch

Adicionalmente arroja un resultado lógico sin tipo informando si la comunicación tuvo éxito. El valor de ok es true si el valor recibido fue entregado por una operación de envío correcta en el canal, o false si se trata de un valor cero generado debido a que el canal está cerrado y vacío.

Conversiones

Las conversiones son expresiones de la forma T(x), donde T es un tipo y x es una expresión que se puede convertir al tipo T.

Conversión = Tipo "(" Expresión [ "," ] ")" .

Si el tipo comienza con el operador * o &lt;-, o si el tipo comienza con la palabra clave func y no tiene una lista de resultados, este se debe encerrar entre paréntesis cuando sea necesario para evitar ambigüedades:

*Punto(p)        // igual que *(Punto(p))
(*Punto)(p)      // p se convierte a *Punto
<-chan int(c)    // igual que <-(chan int(c))
(<-chan int)(c)  // c se convierte a <-chan int
func()(x)        // firma de la función func() x
(func())(x)      // x se convierte a func()
(func() int)(x)  // x se convierte a func() int
func() int(x)    // x se convierte a func() int (sin ambigüedades)

Un valor constante x se puede convertir al tipo T en cualquiera de estos casos:

  • x es representable por un valor de tipo T.
  • x es una constante de coma flotante, T es un tipo de coma flotante y x es representable por medio de un valor de tipo T después de redondeado usando las reglas de redondeo al par IEEE 754. La constante T(x) es el valor redondeado.
  • x es una constante entera y T es un tipo cadena. En este caso, aplica la misma regla que para la x no constante.

La conversión de una constante produce una constante tipada como resultado.

uint(iota)               // valor iota de tipo uint
float32(2.718281828)     // 2.718281828 de tipo float32
complex128(1)            // 1.0 + 0.0i de tipo complex128
float32(0.49999999)      // 0.5 de tipo float32
string('x')              // "x" de tipo string
string(0x266c)           // "♬" de tipo string
MiCadena("foo" + "bar")  // "foobar" de tipo MiCadena
string([]byte{'a'})      // no es una constante: []byte{'a'} no es una
                         // constante
(*int)(nil)              // no es una constante: nil no es una constante,
                         // *int no es un tipo booleano, numérico o string
int(1.2)                 // ilegal: 1.2 no se puede representar como un int
string(65.0)             // ilegal: 65.0 no es una constante entera

Un valor no constante x se puede convertir al tipo T en cualquiera de estos casos:

  • x es asignable a T.
  • x y T tienen idénticos tipos subyacentes.
  • x y T son tipos puntero sin nombre y sus tipos puntero base tienen tipos subyacentes idénticos.
  • ambos x y T son tipos enteros o de coma flotante.
  • ambos x y T son tipos complejos.
  • x es un número entero o un sector de bytes o runes y T es un tipo cadena.
  • x es una cadena y T es un sector de bytes o runes.

Se aplican normas específicas para conversiones (no constantes) entre tipos numéricos o desde y hacia un tipo cadena. Estas conversiones pueden cambiar la representación de x e incurrir en un costo en tiempo de ejecución. Todas las demás conversiones solo cambian el tipo pero no la representación de x.

No hay ningún mecanismo lingüístico para convertir entre punteros y números enteros. Los paquetes inseguros implementan esta funcionalidad en circunstancias restringidas.

Conversiones entre tipos numéricos

Para la conversión de valores numéricos no constantes, se aplican las siguientes reglas:

  1. Cuando conviertas entre tipos enteros, si el valor es un entero con signo, este signo se extiende implícitamente a precisión infinita; de lo contrario, se extiende a cero. Entonces se trunca para encajar en el tamaño del tipo de resultado. Por ejemplo, si v := uint16(0x10F0), entonces, uint32(int8(v)) == 0xFFFFFFF0. La conversión siempre produce un valor válido; no hay ninguna indicación de desbordamiento.
  2. Cuando se convierte un número de coma flotante a entero, la fracción se descarta (truncando a cero).
  3. Al convertir un número entero o de coma flotante a un tipo de coma flotante, o un número complejo a otro tipo complejo, el valor del resultado se redondea a la precisión especificada por el tipo destino. Por ejemplo, el valor de una variable x de tipo float32 se puede almacenar usando precisión adicional más allá de la de un número IEEE-754 de 32 bits, pero float32(x) representa el resultado de redondear el valor de x a precisión de 32 bits. Del mismo modo, x + 0.1 puede utilizar más de 32 bits de precisión, pero float32(x + 0.1) no lo hace.

En todas las conversiones no constantes involucrando valores de coma flotante o complejos, si el tipo del resultado no puede representar el valor de la conversión es éxitosa, pero el valor del resultado depende de la implementación.

Conversiones a y desde un tipo cadena

  1. La conversión de un valor entero con o sin signo a un tipo cadena produce una cadena que contiene la representación UTF-8 del entero. Los valores fuera de rango de caracteres Unicode válidos se convierten a &quot;\uFFFD&quot;.
string('a')       // "a"
string(-1)        // "\ufffd" == "\xef\xbf\xbd"
string(0xf8)      // "\u00f8" == "ø" == "\xc3\xb8"

type MiCadena string

MiCadena(0x65e5)  // "\u65e5" == "日" == "\xe6\x97\xa5"
  1. La conversión de un sector de bytes a un tipo cadena produce una cadena cuyos bytes sucesivos son los elementos del sector.
string([]byte{'h', '\xc3', '\xb8', 'l', 'a'})     // "høla"
string([]byte{})                                  // ""
string([]byte(nil))                               // ""

type MisBytes []byte

string(MisBytes{'h', '\xc3', '\xb8', 'l', 'a'})   // "høla"
  1. La conversión de un sector de runes a un tipo cadena produce una cadena que es la concatenación de los valores runes individuales convertidos a cadenas.
string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{})                         // ""
string([]rune(nil))                      // ""

type MisRunes []rune

string(MisRunes{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"
  1. La conversión de un valor de tipo cadena a un sector de tipo bytes produce un sector cuyos elementos sucesivos son los bytes de la cadena.
[]byte("høla")    // []byte{'h', '\xc3', '\xb8', 'l', 'a'}
[]byte("")        // []byte{}

MisBytes("høla")  // []byte{'h', '\xc3', '\xb8', 'l', 'a'}
  1. La conversión de un valor de tipo cadena a un sector de tipo runes produce un sector que contiene los caracteres Unicode individuales de la cadena.
[]rune(MiCadena("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("")                 // []rune{}
MisRunes("白鵬翔")          // []rune{0x767d, 0x9d6c, 0x7fd4}

Expresiones constantes

Las expresiones constantes pueden contener solo operandos constantes y se evalúan en tiempo de compilación.

Las constantes booleanas sin tipo, numéricas y cadena se pueden utilizar como operandos siempre que sea legal el uso de un operando lógico, numérico o de tipo cadena, respectivamente. A excepción de las operaciones de desplazamiento, si los operandos de una operación binaria son diferentes clases de constantes sin tipo, la operación y, para operaciones no lógicas, el resultado utiliza el tipo que aparece más adelante en esta lista: número entero, rune, de coma flotante o complejo. Por ejemplo, una constante entera sin tipo dividida entre una constante compleja sin tipo produce una constante compleja sin tipo.

Una comparación constante siempre produce una constante lógica sin tipo. Si el operando de la izquierda de una expresión de desplazamiento constante es una constante sin tipo, el resultado es una constante entera; de lo contrario es una constante del mismo tipo que el operando de la izquierda, mismo que debe ser de tipo entero. La aplicación de todos los demás operadores a constantes sin tipo resultan en una constante sin tipo de la misma clase (es decir, una constante booleana, entera, de coma flotante, compleja o cadena).

const a = 2 + 3.0          // a == 5.0   (constante de coma flotante sin tipo)
const b = 15 / 4           // b == 3     (constante entera sin tipo)
const c = 15 / 4.0         // c == 3.75  (constante de coma flotante sin tipo)
const Θ float64 = 3/2      // Θ == 1.0   (tipo float64, 3/2 es una división
                           //                                          entera)
const Π float64 = 3/2.     // Π == 1.5   (tipo float64, 3/2. es la división de
                           //                                   coma flotante)
const d = 1 << 3.0         // d == 8     (constante entera sin tipo)
const e = 1.0 << 3         // e == 8     (constante entera sin tipo)
const f = int32(1) << 33   // ilegal     (constante 8589934592 desbordamiento
                           //                                           int32)
const g = float64(2) >> 1  // ilegal     (float64(2) es una constante de coma
                           //                               flotante con tipo)
const h = "foo" > "bar"    // h == true  (constante lógica sin tipo)
const j = true             // j == true  (constante lógica sin tipo)
const k = 'w' + 1          // k == 'x'   (constante rune sin tipo)
const l = "hi"             // l == "hi"  (constante cadena sin tipo)
const m = string(k)        // m == "x"   (tipo cadena)
const Σ = 1 - 0.707i       //            (constante compleja sin tipo)
const Δ = Σ + 2.0e-4       //            (constante compleja sin tipo)
const Φ = iota*1i - 1/1i   //            (constante compleja sin tipo)

La aplicación de la función incorporada complex a un número entero sin tipo, rune o a constantes de coma flotante produce una constante compleja sin tipo.

const ic = complex(0, c)   // ic == 3.75i  (constante compleja sin tipo)
const iΘ = complex(0, Θ)   // iΘ == 1i     (tipo complex128)

Las expresiones constantes siempre se evalúan con exactitud; los valores intermedios y las constantes en sí mismas pueden requerir precisión significativamente mayor que la compatible con cualquier tipo predeclarado en el lenguaje. Las siguientes son declaraciones lícitas:

const Enorme = 1 << 100         // Enorme == 1267650600228229401496703205376
                                //               (constante entera sin tipo)
const Cuatro int8 = Enorme >> 98  // Cuatro == 4                 (tipo int8)

El divisor de una operación de división o residuo constante no debe ser cero:

3.14 / 0.0   // ilegal: división entre cero

Los valores de constantes tipadas siempre se deben representar con toda precisión como valores del tipo constante. Las siguientes expresiones constantes son ilegales:

uint(-1)       // -1 no se puede representar como uint
int(3.14)      // 3.14 no se puede representar como uint
int64(Enorme)  // 1267650600228229401496703205376 no se puede representar
               //                                              como int64
Cuatro * 300   // operando 300 no se puede representar como int8 (tipo de
               //                                                  Cuatro)
Cuatro * 100   // producto 400 no se puede representar como int8 (tipo de
               //                                                  Cuatro)

La máscara utilizada por el operador complemento unario bit a bit ^ coincide con la regla para los no constantes: la máscara es toda 1s para las constantes sin signo y -1 para las constantes con signo y sin tipo.

^1         // número entero constante sin tipo, igual a -2
uint8(^1)  // ilegal: lo mismo que uint8(-2), -2 no se puede representar
                                                                como uint8
^uint8(1)  // constante uint8 con tipo, igual que
                                             0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // igual que int8(-2)
^int8(1)   // igual que -1 ^ int8(1) = -2

Restricción de implementación: Un compilador puede utilizar redondeo al calcular coma flotante sin tipo o expresiones constantes complejas; ve la restricción de implementación en la sección de constantes. Este redondeo puede causar una expresión constante de coma flotante para ser válida en el contexto de un número entero, incluso si sería integral cuando se calcula usando una precisión infinita.

Orden de evaluación

A nivel de paquete, la iniciación de dependencias determina el orden de evaluación de expresiones de iniciación individuales en las declaraciones de variables. De lo contrario, al evaluar los operandos de una expresión, asignación o instrucción return, todas las llamadas a función, las llamadas a métodos y operaciones de comunicación se evalúan en orden léxico de izquierda a derecha.

Por ejemplo, en la asignación (función local)

y[f()], ok = g(h(), i()+x[j()], <-c), k()

y [f ()], ok = g (h (), i () + `x` [j ()],

a := 1

f := func() int { a++; return a }

x := []int{a, f()}            // x puede ser [1, 2] o [2, 2]: el orden de
                              // evaluación entre a y f() no está
                              // especificado
m := map[int]int{a: 1, a: 2}  // m puede ser {2: 1} o {2: 2}: el orden de
                              // evaluación entre la asignación de los
                              // dos mapas no está especificado
n := map[int]int{a: f()}      // n puede ser {2: 3} o {3: 3}: el orden de
                              // evaluación entre la clave y el valor no
                              // está especificado

A nivel de paquete, la iniciación de dependencias redefine la regla de izquierda a derecha para las expresiones de iniciación individuales, pero no para operandos dentro de cada expresión:

var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }

// las funciones u y v son independientes de todas las otras variables
// y funciones

Las llamadas a funciones suceden en el orden u(), sqr(), v(), f(), v() y g().

Operaciones de coma flotante en una sola expresión se evalúan de acuerdo a la asociatividad de los operadores. Los paréntesis explícitos afectan a la evaluación por razones imperiosas de la asociatividad predeterminada. En la expresión x + (y + z) la adición y + z se realiza antes de sumar x.

Instrucciones

Control de ejecución de instrucciones.

Instrucción =
    Declaración | DeclaEtiquetada | SimpleInstru |
    InstruGo | InstruReturn | InstruBreak | InstruContinue | InstruGoto |
    InstruFallthrough | Bloque | InstruIf | InstruSwitch | InstruSelect |
    InstruFor |    InstruDefer .
SimpleInstru = InstruVacía | DeclaExpresión | InstruEnvío | InstruIncDec |
              Asignación | DeclaVarCorta .

Instrucción de terminación

Una instrucción de terminación es una de las siguientes:

  1. Una instrucción "return" o "goto".
  2. Una llamada a la función integrada panic.
  3. Un bloque en el cual la lista de declaraciones termina en una declaración de terminación.
  4. Una instrucción "if" en la que:
    • la rama "else" está presente y
    • ambas ramas tienen instrucciones de terminación.
  5. Una instrucción "for" en la que:
    • no hay instrucciones "break" refiriéndose a la instrucción "for" y
    • la condición del bucle está ausente.
  6. Una instrucción "switch" en la que:
    • ho hay instrucciones "break" refiriéndose a la instrucción "switch",
    • hay un caso default y
    • la lista de instrucciones en cada caso, incluyendo a default, terminan en una instrucción de terminación o una posiblemente etiquetada instrucción "fallthrough".
  7. Una instrucción "select" en la que:
    • no hay declaraciones "break" refiriéndose a la instrucción "select" y
    • las listas de instrucciones en cada caso, incluyendo a default si está presente, terminan en una instrucción de terminación.
  8. Una instrucción etiquetada vinculada a una instrucción de terminación.

Todas las demás declaraciones no están terminando.

Una lista de instrucciones termina en una instrucción de terminación si la lista no está vacía y su instrucción final está terminando.

Instrucciones vacías

La instrucción vacía no hace nada.

InstruVacía = .

Instrucciones etiquetadas

Una instrucción etiquetada puede ser el destino de una instrucción goto, break o continue.

InstruEtiquetada = Etiqueta ":" Declaración .
Etiqueta         = identificador .
Error: log.Panic("encontré un error")

Expresiones instrucción

A excepción de funciones integradas específicas, las llamadas a función, métodos y operaciones de recepción pueden aparecer en el contexto de una instrucción. Tales instrucciones pueden estar entre paréntesis.

InstruExpresión = Expresión .

Las siguientes funciones incorporadas no están permitidas en el contexto de una instrucción:

append cap complex imag len make new real
unsafe.Alignof unsafe.Offsetof unsafe.Sizeof
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo")  // ilegal si len es la función integrada

Instrucciones de envío

Una instrucción de envío transmite un valor en un canal. La expresión canal debe ser de tipo canal, la dirección del canal debe permitir las operaciones de envío y el tipo de valor a enviar debe ser asignable al tipo de elemento del canal.

InstruEnvío = Canal "<-" Expresión .
Canal      = Expresión .

Tanto el canal como la expresión de valor se evalúan antes de que comience la comunicación. Se bloquea la comunicación hasta que pueda proceder el envío. Un envío en un canal sin búfer puede proceder si un receptor está listo. Un envío en un canal con búfer puede proceder si hay espacio en el búfer. Un envío en un canal cerrado procede provocando pánico en tiempo de ejecución. Un envío a un canal nil se bloquea para siempre.

ch <- 3  // envía el valor 3 al canal ch

Instrucciones IncDec

Las instrucciones "++" y "--" incrementan o disminuyen sus operandos por la constante sin tipo 1. Al igual que con una asignación, el operando debe ser direccionable o una expresión de índice de mapa.

InstruIncDec = Expresión ( "++" | "--" ) .

Las siguientes instrucciones de asignación son semánticamente equivalentes:

Instrucción IncDec  Asignación
x++                 x += 1
x--                 x -= 1

Asignaciones

asignación    = ListaExpresión op_asignación ListaExpresión .
op_asignación = [ op_adi | op_mul ] "=" .

Cada operando del lado izquierdo debe ser direccionable, una expresión de índice de mapa o (solo para asignaciones =) el identificador blanco. Los operandos pueden estar entre paréntesis.

x = 1
*p = f()
a[i] = 23
(k) = <-ch  // lo mismo que: k = <-ch

Una operación de asignación x op= y donde op es una operación aritmética binaria es equivalente a x = x op y sino que solo evalúa a x una vez. La construcción op= es un solo símbolo. En las operaciones de asignación, tanto la lista de expresiones izquierda como la derecha deben contener exactamente una expresión de valor único y la expresión de la izquierda no debe ser el identificador blanco.

a[i] <<= 2
i &^= 1<<n

Una tupla de asignación establece los elementos individuales de una operación de varios valores a una lista de variables. Hay dos formas. En la primera, el operando de la derecha es una sola expresión de varios valores, tal como una llamada a función, una operación de canal o mapa, o un tipo aserción. El número de operandos en el lado de la mano izquierda debe coincidir con el número de valores. Por ejemplo, si f es una función que devuelve dos valores:

x, y = f()

asigna el primer valor a x y el segundo a y. En la segunda forma, el número de operandos de la izquierda debe ser igual al número de expresiones de la derecha, cada uno de los cuales debe ser de un solo valor y la enésima expresión de la derecha se asigna al enésimo operando de la izquierda:

uno, dos, tres = '一', '二', '三'

El identificador blanco proporciona una manera de ignorar los valores del lado derecho en una asignación:

_ = x       // evalúa x pero lo descarta
x, _ = f()  // evalúa f() pero desecha el valor del resultado

La asignación procede en dos fases. En primer lugar, los operandos de las expresiones de índice e indirecciones de puntero (incluyendo indirecciones de puntero implícitas en selectores) a la izquierda y las expresiones de la derecha todas se evalúan en el orden habitual. En segundo lugar, las tareas se llevan a cabo en orden de izquierda a derecha.

a, b       = b, a  // intercambia a y b
x         := []int{1, 2, 3}
i         := 0
i, x[i]    = 1, 2  // fija i = 1, x[0] = 2
i          = 0
x[i], i    = 2, 1  // fija x[0] = 2, i = 1
x[0], x[0] = 1, 2  // fija x[0] = 1, luego x[0] = 2 (por tanto x[0] == 2
                   //                                           al final)
x[1], x[3] = 4, 5  // fija x[1] = 4, luego ajusta el pánico x[3] = 5.

type Punto struct { x, y int }

var p *Punto

x[2], p.x   = 6, 7  // fija x[2] = 6, luego ajusta el pánico p.x = 7
i           = 2
x           = []int{3, 5, 7}

for i, x[i] = range x {  // fija i, x[2] = 0, x[0]
    break
}

// después de este bucle, i == 0 y x == []int{3, 5, 3}

En las asignaciones, cada valor debe ser asignable al tipo del operando al que se le asigna, con los siguientes casos especiales:

  1. Cualquier valor tipado se puede asignar al identificador blanco.
  2. Si se asigna una constante sin tipo a una variable de tipo interfaz o al identificador blanco, la constante primero se convierte a su tipo predeterminado.
  3. Si se asigna un valor lógico sin tipo a una variable de tipo interfaz o al identificador blanco, esta primero se convierte al tipo bool.

Instrucciones if

Las instrucciones "if" especifican la ejecución condicional de dos ramas de acuerdo con el valor de una expresión booleana. Si la expresión se evalúa como verdadera, la rama "if" se ejecuta, de lo contrario, si está presente, se ejecuta la rama "else".

InstruIf = "if" [ SimpleInstru ";" ] Bloque Expresión [ "else" ( InstruIf |
                                                                Bloque ) ] .
if x > máx {
    x = máx
}

La expresión puede estar precedida por una declaración simple, que se ejecuta antes de evaluar la expresión.

if x := f(); x < y {
    return x
} else if x > z {
    return z
} else {
    return y
}

Instrucciones switch

Las instrucciones "switch" proporcionan la ejecución múltivía. Una expresión o especificador de tipo se compara con los "casos" en el interior del "switch" para determinar qué rama se ejecuta.

InstruSwitch = InstruExpreSwitch | InstruTipoSwitch .

Hay dos formas: expresiones switch y tipos switch. En una expresión switch, los casos contienen expresiones que se comparan con el valor de la expresión switch. En un tipo switch, los casos contienen tipos que se comparan contra el tipo de una expresión switch especialmente anotada.

Expresiones switch

En una expresión switch, se evalúa la expresión switch y las expresiones de casos, que no necesitan ser constantes, se evalúan de izquierda a derecha y de arriba hacia abajo; el primero que es igual a la expresión switch desencadena la ejecución de las instrucciones del caso asociado; los demás casos se omiten. En el supuesto de que no haya coincidencia se ejecutan las instrucciones del caso "default". Como máximo puede haber un caso default y puede aparecer en cualquier parte de la instrucción "switch". Una expresión switch faltante es equivalente al valor lógico true.

InstruExpreSwitch = "switch" [ SimpleInstru ";" ] [ Expresión ] "{" {
                                                  ExpreCláusulaCase } "}" .
ExpreCláusulaCase = ExpreCasoSwitch ":" ListaInstrucciones .
ExpreCasoSwitch   = "case" ListaExpresión | "default" .

En una cláusula case o default, la última instrucción no vacía puede ser una (posiblemente etiquetada) instrucción "fallthrough" para indicar que el control debe fluir desde el final de esta cláusula a la primera declaración de la siguiente cláusula. De lo contrario, el control fluye al final de la instrucción "switch". Una instrucción "fallthrough" puede aparecer como la última declaración de todas, pero la última cláusula de una expresión switch.

La expresión puede estar precedida por una instrucción simple, que se ejecuta antes de evaluar la expresión.

switch etiqueta {
  default: s3()
  case 0, 1, 2, 3: s1()
  case 4, 5, 6, 7: s2()
}

switch x := f(); {  // falta expresión switch significa "true"
  case x < 0: return -x
  default: return x
}

switch {
  case x < y: f1()
  case x < z: f2()
  case x == 4: f3()
}

Tipos switch

Un tipo switch compara tipos en lugar de valores. Por lo demás es similar a una expresión switch. Se caracteriza por una expresión switch especial que tiene la forma de un tipo aserción utilizando la palabra reservada type en lugar de un tipo real:

switch x.(tipo) {
// casos
}

Entonces los casos coinciden los tipos T reales contra el tipo dinámico de la expresión x. Al igual que con los tipos aserción, x debe ser de tipo interfaz y cada tipo no interfaz T enumerado en un caso debe implementar el tipo de x.

InstruTipoSwitch  = "switch" [ SimpleInstru ";" ] ProtecTipoSwitch "{" {
                                                    TipoCláusulaCase } "}" .
ProtecTipoSwitch  = [ identificador ":=" ] ExprePrimaria "." "(" "type" ")" .
TipoCláusulaCase  = TipoCasoSwitch ":" ListaInstrucciones .
TipoCasoSwitch    = "case" ListaTipo | "default" .
ListaTipo         = Tipo { "," Tipo } .

El ProtecTipoSwitch puede incluir una declaración corta de variable. Cuando se utiliza esta forma, la variable se declara al comienzo del bloque implícito en cada cláusula. En las cláusulas con un caso enumerando exactamente un tipo, la variable tiene ese tipo; de lo contrario, la variable tiene el tipo de la expresión en el ProtecTipoSwitch.

El tipo en un caso puede ser nil; ese caso se utiliza cuando la expresión en el ProtecTipoSwitch es un valor interfaz nil.

Dada una expresión x de tipo interface{}, el siguiente tipo switch:

switch i := x.(tipo) {
  case nil:
    printString("x es nil")                // el tipo de i es el tipo de
                                           //            x(interface{})
  case int:
    printInt(i)                            // el tipo de i es int
  case float64:
    printFloat64(i)                        // el tipo de i es float64
  case func(int) float64:
    printFunction(i)                       // el tipo de i es func(int) float64
  case bool, string:
    printString("tipo es bool o string")   // el tipo de i es el tipo de
                                           //            x(interface{})
default:
    printString("deconozco el tipo")      // el tipo de i es el tipo de
                                           //           x(interface{})
}

Se podría reescribir de la siguiente manera:

v := x  // x se evalúa exactamente una vez

if v == nil {
    i := v                                 // el tipo de i es el tipo de
                                           //            x(interface{})
    printString("x es nil")
} else if i, isInt := v.(int); isInt {
    printInt(i)                            // el tipo de i es int
} else if i, isFloat64 := v.(float64); isFloat64 {
    printFloat64(i)                        // el tipo de i es float64
} else if i, isFunc := v.(func(int) float64); isFunc {
    printFunction(i)                       // el tipo de i es func(int)
                                           //                      float64
} else {
    _, isBool := v.(bool)
    _, isString := v.(string)
    if isBool || isString {
        i := v                             // el tipo de i es el atipo de
                                           //            x(interface{})
        printString("tipo es bool o string")
    } else {
        i := v                             // el tipo de i es el tipo de
                                           //            x(interface{})
        printString("desconozco el tipo")
    }
}

El protector del tipo switch puede estar precedido por una declaración simple, que se ejecuta antes de que se evalúe el protector.

La declaración "fallthrough" no está permitida en un tipo switch.

Instrucciones for

Una instrucción "for" especifica la ejecución repetida de un bloque. La iteración es controlada por una condición, una cláusula "for" o una cláusula "range".

InstruFor  = "for" [ Condición | CláusulaFor | CláusulaRange ] Bloque .
Condición  = Expresión .

En su forma más simple, una instrucción "for" especifica la ejecución repetida de un bloque, siempre que una condición booleana se evalúe como verdadera. La condición se evalúa antes de cada iteración. Si la condición está ausente, es equivalente al valor lógico true.

for a < b {
    a *= 2
}

Una instrucción "for" con una CláusulaFor también es controlada por su condición, pero además, como asignación puede especificar una expresión inicio y una destino, así como una declaracion de incremento o decremento. La expresión de inicio puede ser una declaración corta de variables, pero la declaración destino no debe. Las variables declaradas por la declaración de inicio se vuelven a utilizar en cada iteración.

CláusulaFor  = [ ExpreInicio ] ";" [ Condición ] ";" [ ExpreDestino ] .
ExpreInicio  = SimpleInstru .
ExpreDestino = SimpleInstru .
for i := 0; i < 10; i++ {
    f(i)
}

Si no está vacía, la declaración de inicio se ejecuta una vez antes de evaluar la condición en la primera iteración; la declaración destino se evalúa después de cada ejecución del bloque (y únicamente si el bloque se ejecuta). Cualquier elemento de la CláusulaFor puede estar vacío pero los puntos y comas son obligatorios a menos que solo haya una condición. Si la condición está ausente, es equivalente al valor lógico true.

for cond { S() }    es lo mismo que    for ; cond ; { S() }
for      { S() }    es lo mismo que    for true     { S() }

Una instrucción "for" con una cláusula "range" itera a través todas las entradas de un arreglo, sector, cadena, mapa o los valores recibidos en un canal. Por cada entrada esta asigna valores de iteración a las variables de iteración correspondientes si están presentes y luego ejecuta el bloque.

CláusulaRange = [ ListaExpresión "=" | ListaIdentificador ":=" ]
                                                       "range" Expresión .

La expresión de la derecha en la cláusula "range" se conoce como la expresión de rango, que bien puede ser un arreglo, puntero a un arreglo, sector, cadena, mapa o canal que permite operaciones de recepción. Al igual que con una asignación, si están presentes los operandos de la izquierda deben ser direccionables o expresiones índice de mapa; estos denotan las variables de iteración. Si la expresión rango es un canal, por lo menos se permite una variable de iteración, de lo contrario puede haber un máximo de dos. Si la última variable de iteración es el identificador blanco, la cláusula rango es equivalente a la misma cláusula sin ese identificador.

La expresión de rango se evalúa una vez antes de comenzar el bucle, con una excepción: si la expresión rango es un vector o un puntero a un arreglo y como máximo está presente una variable de iteración, sólo se evalúa la longitud de la expresión de rango; si esta longitud es constante, por definición no se evaluará la expresión en si misma.

Las llamadas a función de la izquierda son evaluadas una vez por iteración. Por cada iteración, los valores de iteración se producen como sigue si las respectivas variables de iteración están presentes:

Expresión range                            1er valor           2do valor
arreglo o sector  a  [n]E, *[n]E, o []E    índice    i  int    a[i]       E
cadena            s  tipo string           índice    i  int    ve rune abajo
mapa              m  map[K]V               clave     k  K      m[k]       V
canal             c  chan E, <-chan E      elemento  e  E
  1. Para un arreglo, puntero a arreglo o valor del sector a, los valores índice de iteración se producen en orden creciente, comenzando en el índice del elemento 0. Si a lo sumo está presente una variable de iteración, el bucle de iteración range produce valores de 0 hasta len(a)-1 y no tiene índice en el arreglo o sector en sí mismo. Para un sector nil, el número de iteraciones es 0.
  2. Para un valor de cadena, la cláusula "range" itera sobre los caracteres Unicode en la cadena a partir del índice del byte 0. En sucesivas iteraciones, el valor del índice será el índice del primer byte de los sucesivos caracteres codificación UTF-8 en la cadena y el segundo valor, de tipo rune, será el valor del carácter correspondiente. Si la iteración se encuentra con una secuencia UTF-8 no válida, el segundo valor será 0xFFFD, el carácter Unicode de reemplazo y la siguiente iteración avanzará un solo byte en la cadena.
  3. La orden de iteración sobre mapas no se especifica y no se garantiza que sea la misma de una iteración a la siguiente. Si se eliminan entradas correlacionadas que aún no se han alcanzado durante la iteración, no se producirán los valores de iteración correspondientes. Si se crean entradas en el mapa durante la iteración, esa entrada se puede producir durante la iteración o se puede omitir. La elección puede variar para cada entrada creada y de una iteración a la siguiente. Si el mapa es nil, el número de iteraciones es 0.
  4. Para canales, los valores de iteración producidos son los valores sucesivos enviados por el canal hasta que el canal esté cerrado. si el canal es nil, la expresión range se bloquea para siempre.

Los valores de iteración se asignan a las respectivas variables de iteración como en una instrucción de asignación.

Las variables de iteración se pueden declarar por cláusula "range" utilizando una forma de declaración corta de variables (:=). En este caso, sus tipos se fijan a los tipos de los respectivos valores de iteración y su ámbito es el bloque de la declaración "for"; estas se vuelven a utilizar en cada iteración. Si las variables de iteración se declaran fuera de la declaración "for", después de la ejecución sus valores serán los de la última iteración.

var datosdeprueba *struct {
    a *[7]int
}

for i, _ := range datosdeprueba.a {
    // datosdeprueba.a nunca se evalúa; len(datosdeprueba.a) es constante
    // i rangos desde 0 hasta 6
    f(i)
}

var a [10]string
for i, s := range a {
    // el tipo de i es int
    // el tipo de s es string
    // s == a[i]
    g(i, s)
}

var clave string
var val interface {}  // el tipo del valor de m es asignable a val

m := map[string]int{"lun":0, "mar":1, "mie":2, "jue":3, "vie":4,
                    "sáb":5, "dom":6}

for clave, val = range m {
    h(clave, val)
}
// clave == última clave encontrada en la iteración del mapa
// val == map[clave]

var ch chan Trabajo = productor()
for w := range ch {
    hazTrabajo(w)
}

// canal vacío
for range ch {}

Instrucciones go

Una instrucción "go" comienza la ejecución de una llamada a función como un hilo de control concurrente independiente, o rutinago, dentro del mismo espacio de direcciones.

InstruGo = "go" Expresión .

La expresión debe ser una llamada a función o método; no puede estar entre paréntesis. Las llamadas a funciones integradas están restringidas en cuanto a las declaraciones de expresión.

El valor de la función y los parámetros se evalúan como de costumbre en la llamada a la rutinago, pero a diferencia de una llamada regular, la ejecución del programa no espera a que la función invocada termine. En lugar de ello, la función comienza a ejecutarse de forma independiente en una nueva rutinago. Cuando la función termina, su rutinago también termina. Si la función tiene algúnos valores de retorno, serán descartados en cuanto la función termine.

go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true; }} (c)

Instrucciones select

Una instrucción "select" elige cual de un conjunto de posibles operaciones de envío o recepción procederá. Es similar a una declaración "switch" pero en la cual todos los casos se refieren a operaciones de comunicación.

InstruSelect         = "select" "{" { CláusulaComunicación } "}" .
CláusulaComunicación = CasoComunicación ":" ListaInstrucciones .
CasoComunicación     = "case" ( InstruEnvío | InstruRecep ) | "default" .
InstruRecep  = [ ListaExpresión "=" | ListaIdentificador ":=" ] ExpreRecep .
ExpreRecep   = Expresión .

Un caso con una InstruRecep puede asignar el resultado de una ExpreRecep a una o dos variables, las cuales se pueden declarar utilizando una declaración corta de variables. La ExpreRecep debe ser una (posiblemente entre paréntesis) operación de recepción. Como máximo puede haber un caso default y puede aparecer en cualquier parte de la lista de casos.

La ejecución de una declaración "select" procede en varias etapas:

  1. Para todos los casos en la declaración, los operandos de canales de operaciones de recepción y el canal y las expresiones de envío del lado de la mano derecha son evaluadas exactamente una vez, en el orden de la fuente, al entrar en la declaración "select". El resultado es un conjunto de canales para recibir desde o enviar a, y los valores correspondientes a enviar. Cualquier efecto secundario en la evaluación se producirá independientemente de que (si las hay) se seleccione la operación de comunicación para proceder. Las expresiones en el lado izquierdo de una InstruRecep con una declaración corta de variables o asignación aún no se han evaluado.
  2. Si una o más de las comunicaciones puede proceder, se elige una sola, la que pueda proceder a través de una selección pseudoaleatoria uniforme. De lo contrario, si hay un caso predeterminado, se elige ese caso. Si no hay ningún caso predeterminado, la declaración "Select" se bloquea hasta que al menos una de las comunicaciones pueda continuar.
  3. A menos que el caso seleccionado sea el caso predeterminado, se ejecuta la operación de comunicación respectiva.
  4. Si el caso seleccionado es una InstruRecep con una declaración corta de variable o es una asignación, las expresiones de la parte izquierda se evalúan y el valor (o valores) recibidos serán asignados.
  5. Se ejecuta la lista de instrucciones del caso seleccionado.

Puesto que la comunicación en canales nil nunca puede proceder, una "select" con únicamente canales nil y sin un caso predeterminado se bloquea por siempre.

var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int

select {
  case i1 = <-c1:
    print("recibí ", i1, " desde c1\n")
  case c2 <- i2:
    print("envié ", i2, " a c2\n")
  case i3, ok := (<-c3):  // igual que: i3, ok := <-c3
    if ok {
        print("recibí ", i3, " desde c3\n")
    } else {
        print("c3 está cerrado\n")
    }
  case a[f()] = <-c4:
    // mismo que:
    // case t := <-c4
    //    a[f()] = t
  default:
    print("no hay comunicación\n")
}

for {  // envía secuencias aleatorias de bits a c
    select {
      case c <- 0:  // observa: no hay instrucción,
                    // no hay fallthrough,
                    // no hay repliegue de casos
      case c <- 1:
    }
}

select {}  // bloquea por siempre

Instrucciones return

Una instrucción "return" en una función F termina la ejecución de F y opcionalmente proporciona uno o más valores como resultado. Cualquier función diferida por F se ejecuta antes de que F regrese a su invocador.

InstruReturn = "return" [ ListaExpresión ] .

En una función sin un tipo de resultado, una instrucción "return" no deberá especificar ningún valor de resultado.

func sinResultado() {
    return
}

Hay tres formas de devolver valores de una función con un tipo de resultado:

  1. El valor o valores de retorno se pueden enumerar explícitamente en la instrucción "return". Cada expresión debe ser de un solo valor y asignable al elemento correspondiente del tipo de resultado de la función.

     func fSimple() int {
         return 2
     }
    
     func fCompleja() (re float64, im float64) {
         return -7.0, -4.0
     }
    
  2. La lista de expresiones en la instrucción "return" puede ser una sola llamada a una función multivalores. El efecto es como si cada valor devuelto por la función se asignara a una variable temporal con el tipo del valor respectivo, seguido de una instrucción "return" enumerando estas variables, momento en que se aplican las reglas del caso anterior.

    func fCompleja2() (re float64, im float64) {
        return fCompleja()
    }
    
  3. La lista de expresiones puede estar vacía si el tipo de resultado de la función especifica nombres para sus parámetros de resultado. Los parámetros de resultado actúan como variables locales ordinarias y la función le puede asignar valores cuando sea necesario. La instrucción "return" devuelve los valores de esas variables.

    func fCompleja3() (re float64, im float64) {
        re = 7.0
        im = 4.0
        return
    }
    
    func (devnull) Escribe(p []byte) (n int, _ error) {
        n = len(p)
        return
    }
    

Independientemente de la forma en que se declaren, todos los valores de los resultados se inician a los valores cero para su tipo al entrar a la función. Una instrucción "return" que especifica los resultados establece los parámetros de resultado antes de ejecutar cualquiera de las funciones diferidas.

Restricción de implementación: Un compilador podrá invalidar una lista de expresiones vacía en una instrucción "return" si una entidad diferente (constante, tipo o variable) con el mismo nombre que un parámetro de resultado está en el ámbito del lugar de la devolución.

func f(n int) (res int, err error) {
    if _, err := f(n-1); err != nil {
        return  // func f (n int) (res int, err error) {
    if _, err: = f (n-1); err! = nil {
        return // instrucción return no válida: err es ensombrecido
    }
    return
}

Instrucciones break

Una instrucción "break" termina la ejecución de la instrucción "for", "switch" o "select" más interna dentro de la misma función.

InstruBreak = "break" [ Etiqueta ] .

Si hay una etiqueta, debe ser la de una instrucción envolvente "for", "switch" o "select", que es en dónde se tiene que terminar la ejecución.

BucleExterno:
    for i = 0; i < n; i++ {
        for j = 0; j < m; j++ {
            switch a[i][j] {
            case nil:
                estado = Error
                break BucleExterno
            case item:
                estado = Encontrado
                break BucleExterno
            }
        }
    }

Instrucciones continue

Una instrucción "continue", comienza la siguiente iteración del bucle "for" más interno en su posterior instrucción. El bucle "for" debe estar dentro de la misma función.

InstruContinue = "continue" [ Etiqueta ] .

Si hay una etiqueta, debe ser la de una instrucción "for" envolvente y que sea en la que tiene que avanzar la ejecución.

BucleRenglón:
    for y, renglón := range renglones {
        for x, datos := range renglón {
            if datos == finDeRenglón {
                continue BucleRenglón
            }
            renglón[x] = datos + parcial(x, y)
        }
    }

Instrucciones goto

Una instrucción "goto" transfiere el control a la declaración con la etiqueta correspondiente dentro de la misma función.

InstruGoto = "goto" Etiqueta .
goto Error

La ejecución de la instrucción "goto" no debe provocar que ninguna variable entre en el ámbito que no estaba ya en su alcance en el punto de la goto. Por ejemplo, veamos esta declaración:

goto L  // MAL
v := 3
L:

es errónea porque salta a la etiqueta L omitiendo la creación de v.

Una instrucción "goto" declarada fuera de un bloque no puede saltar a una etiqueta dentro de este bloque. Por ejemplo, veamos esta declaración:

if n%2 == 1 {
    goto L1
}

for n > 0 {
    f()
    n--
L1:
    f()
    n--
}

es errónea porque la etiqueta L1 está dentro del bloque de la instrucción "for", pero la instrucción "goto" no.

Instrucciones fallthrough

Una instrucción "fallthrough" transfiere el control a la primera declaración de la siguiente cláusula caso en una expresión "switch". Solo se puede utilizar como la instrucción no vacía final en dicha cláusula.

InstruFallthrough = "fallthrough" .

Instrucciones defer

Una instrucción "defer" invoca a una función cuya ejecución se difiere hasta momentos antes de que la función regrese, ya sea porque la función envolvente ejecutó una instrucción return, se alcanzó el final del cuerpo de la función o porque la rutinago correspondiente está entrando en pánico.

InstruDefer = "defer" Expresión .

La expresión debe ser una llamada a función o método; no puede estar entre paréntesis. Las llamadas a funciones integradas están restringidas en cuanto a las declaraciones de expresión.

Cada vez que se ejecuta una declaración "defer", el valor de la función y los parámetros de la llamada se evalúan de la manera usual y se guarda de nuevo, pero la función real no se invoca. En su lugar, las funciones diferidas se invocan inmediatamente antes de que la función envolvente regrese, en orden inverso al que fueron diferidas. Si un valor de función diferida evalúa a nil, se entra en pánico cuando la función se invoca, no cuando se ejecute la instrucción "defer".

Por ejemplo, si la función diferida es una función literal y la función que la rodea tiene parámetros nombrados como resultado esos están en el ámbito de la función literal, la función diferida puede acceder y modificar los parámetros de resultado antes de que sean devueltos. Si la función diferida no tiene ningún valor de retorno, estos serán descartados al terminar la función. (Ve también la sección sobre el manejo de pánico).

bloquea(l)

defer desbloquea(l)  // el desbloqueo ocurre antes que la
                     // función envolvente regrese

// imprime 3 2 1 0 antes de que la función envolvente regrese
for i := 0; i <= 3; i++ {
    defer fmt.Print(i)
}

// f devuelve 1
func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

Funciones integradas

Las funciones incorporadas son predeclaradas. Se llaman como cualquier otra función, pero algunas de ellas aceptan un tipo en lugar de una expresión como primer argumento.

Las funciones incorporadas no tienen tipos estándar Go, por lo que sólo pueden aparecer en expresiones de llamada; no se pueden utilizar como valores de función.

close

Para un canal c, la función incorporada close(c) registra que no más valores serán enviados en el canal. Es un error si c únicamente es un canal de recepción. Enviar a o cerrar un canal cerrado provoca pánico en tiempo de ejecución. Cerrar el canal nil también provoca pánico en tiempo de ejecución. Después de llamar a close y después de que se hayan recibido todos los valores enviados anteriormente, las operaciones de recepción devolverán el valor cero para el tipo de canal sin bloquearlo. Las operaciones de recepción multivalor devuelven un valor recibido junto con una indicación de si el canal está cerrado.

Longitud y capacidad

Las funciones incorporadas len y cap toman argumentos de varios tipos y devuelven un resultado de tipo int. La implementación garantiza que el resultado siempre encaja en un int.

Llamada   Tipo de argumento   Resultado
len(s)    tipo string         longitud de la cadena en bytes
          [n]T, *[n]T         longitud del arreglo    (== n)
          []T                 longitud del sector
          map[K]T             longitud del mapa (el número de claves definido)
          chan T              número de elementos en cola en el búfer del canal
cap(s)    [n]T, *[n]T         Longitud del arreglo (== n)
          []T                 capacidad del sector
          chan T              capacidad del búfer del canal

La capacidad de un sector es el número de elementos para los cuales hay espacio reservado en el arreglo subyacente. En cualquier momento mantiene la siguiente relación:

0 <= len(s) <= cap(s)

La longitud de un sector nil, mapa o canal es 0. La capacidad de un sector o canal nil es 0.

La expresión len(s) es constante si s es una cadena constante. Las expresiones len(s) y cap(s) son constantes si el tipo de s es un arreglo o puntero a un arreglo y la expresión s no contiene llamadas a función en un canal receptor o (no constante); en ese caso, s no se evalúa. De lo contrario, las invocaciones de len y cap no son constantes y s se evalúa.

const (
    c1 = imag(2i)                    // imag(2i) = 2.0 es una constante
    c2 = len([10]float64{2})         // [10]float64{2} no contiene
                                     // llamadas a función
    c3 = len([10]float64{c1})        // [10]float64{c1} no contiene
                                     // llamadas a función
    c4 = len([10]float64{imag(2i)})  // imag(2i) es una constante y no
                                     // se emite llamada a función
    c5 = len([10]float64{imag(z)})   // no válida: imag(z) es una
                                     // llamada a función (no constante)
)

var z complex128

Asignación

La función incorporada new toma un tipo T, reserva almacenamiento para una variable de ese tipo en tiempo de ejecución y devuelve un valor de tipo *T que apunta a ella misma. La variable se inicia como se describe en la sección sobre los valores iniciales.

new(T)

Por ejemplo:

type S struct { a int; b float64 }
new(S)

reserva almacenamiento para una variable de tipo S, lo inicia (a=0, b=0.0) y devuelve un valor de tipo *S que contiene la dirección de esa ubicación.

Creando sectores, mapas y canales

La función integrada make toma un tipo T, el cual debe ser un tipo sector, mapa o canal, opcionalmente seguido de una lista de expresiones de tipos específicos. Devuelve un valor de tipo T (no *T). La memoria se inicia como se describe en la sección sobre los valores iniciales.

Llamada          Tipo de T  Resultado
make(T, n)       sector     sector de tipo T con longitud n y capacidad n
make(T, n, m)    sector     sector de tipo T con longitud n y capacidad m
make(T)          mapa       mapa de tipo T
make(T, n)       mapa       mapa de tipo T con espacio inicial para n elementos
make(T)          canal      canal sin búfer de tipo T
make(T, n)       canal      canal con búfer de tipo T, tamaño del búfer n

Los argumentos de tamaño n y m deben ser de tipo entero o sin tipo. Un argumento constante para tamaño debe no ser negativo y representable por un valor de tipo int. Si se proporcionan ambos n y m y son constantes, entonces n debe ser mayor que m. Si n es negativo o mayor que m en tiempo de ejecución, se produce pánico en tiempo de ejecución.

s := make([]int, 10, 100)       // sector con len(s) == 10, cap(s) == 100
s := make([]int, 1e3)           // sector con len(s) == cap(s) == 1000
s := make([]int, 1<<63)         // ilegal: len(s) no es representable por un valor de tipo int
s := make([]int, 10, 0)         // ilegal: len(s) > cap(s)
c := make(chan int, 10)         // canal con un búfer de tamaño 10
m := make(map[string]int, 100)  // mapa con espacio inicial para 100 elementos

Anexando a y copiando sectores

Las funciones incorporadas append y copy ayudan en las operaciones de sector comunes. Para ambas funciones, el resultado es independiente de si la memoria referenciada por los argumentos se superpone.

La función variadica append añade cero o más valores x a s de tipo S, el cual debe ser un tipo de sector y devolver el sector resultante, también de tipo S. Los valores x se pasan a un parámetro de tipo ...T dónde T es el tipo de elemento de S y se aplican las respectivas reglas para el paso de parámetros. Como caso especial, append también acepta un primer argumento asignable al tipo []byte con un segundo argumento de tipo cadena seguido por .... Esta forma agrega los bytes de la cadena.

append(s S, x ...T) S  // T es el tipo de elemento de S

Si la capacidad de s no es lo suficientemente grande como para ajustarse a los valores adicionales, append asigna un nuevo arreglo subyacente lo suficientemente grande para que se ajuste tanto a los elementos existentes del sector como a los valores adicionales. De lo contrario, append reutiliza el arreglo subyacente.

s0 := []int{0, 0}
s1 := append(s0, 2)                // agrega un único elemento
                                   // s1 == []int{0, 0, 2}

s2 := append(s1, 3, 5, 7)          // agrega múltiples elementos
                                   // s2 == []int{0, 0, 2, 3, 5, 7}

s3 := append(s2, s0...)            // agrega un sector
                                   // s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}

s4 := append(s3[3:6], s3[2:]...)   // agrega superponiendo un sector
                                   // s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}

t = append(t, 42, 3.1415, "foo")   // t == []interface{}{42, 3.1415, "foo"}

var b []byte

b = append(b, "bar"...)            // añade contenido a la cadena
                                   // b == []byte{'b', 'a', 'r' }

La función copy copia elementos del sector desde una fuente src a un destino dst y devuelve el número de elementos copiados. Ambos argumentos deben tener idéntico tipo de elemento T y deben ser asignables a un sector de tipo []T. El número de elementos copiados es el mínimo de len(src) y len(dst). Como caso especial, copy también acepta un argumento destino asignable al tipo []byte con un argumento fuente de tipo cadena. Esta forma copia los bytes de la cadena al sector de byte.

copy(dst, src []T) int
copy(dst []byte, src string) int

Ejemplos:

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])            // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "¡Hola, mundo!")  // n3 == 5, b == []byte("¡Hola")

Eliminando elementos de mapa

La función incorporada delete elimina el elemento con la clave k de un mapa m. El tipo de k debe ser asignable al tipo de la clave de m.

delete(m, k)  // elimina el elemento m[k] del mapa m

Si el mapa m es nil o el elemento m[k] no existe, delete no es operativo.

Manipulando números complejos

Tres funciones para montar y desmontar números complejos. La función incorporada complex construye un valor complejo a partir de una parte real de coma flotante y una parte imaginaria, mientras que real e imag extraen las partes real e imaginaria de un valor complejo.

complex(parteReal, parteImaginaria floatT) tipoComplejo
real(tipoComplejo) floatT
imag(tipoComplejo) floatT

El tipo de los argumentos y valor de retorno corresponde. Para complex, los dos argumentos deben ser del mismo tipo, coma flotante y el tipo de retorno es de tipo complejo con los correspondientes componentes de coma flotante: complex64 para float32, complex128 para float64. Juntas las funciones real e imag forman la inversa, por lo que para un complejo de valor de z, z == complex(real(z), imag(z)).

Si todos los operandos de estas funciones son constantes, el valor de retorno es una constante.

var a = complex(2, -2)             // complex128
var b = complex(1.0, -1.4)         // complex128
x := float32(math.Cos(math.Pi/2))  // float32
var c64 = complex(5, -x)           // complex64
var im = imag(b)                   // float64
var rl = real(c64)                 // float32

Manejando pánicos

Dos funciones incorporadas, panic y recover, ayudan en la presentación de informes y manipulación de pánicos en tiempo de ejecución y las condiciones de error definidas por el programa.

func panic(interface{})

func recover() interface{}

Durante la ejecución de una función F, una llamada explícita a panic o un pánico en tiempo de ejecución finaliza la ejecución de F. Cualquier función diferida por F se ejecuta entonces de la manera usual. A continuación, las funciones diferidas a cargo del llamador F&#39;s se ejecutan y así sucesivamente hasta cualquier diferida por la función de nivel superior en la rutinago en ejecución. En ese momento, el programa se termina y se reporta la condición de error, incluyendo el valor del argumento para entrar en pánico. Esta secuencia de terminación se conoce como entrar en pánico (panicking en inglés).

panic(42)
panic("inalcanzable")
panic(Error("no se puede interpretar"))

La función recover permite que un programa maneje el comportamiento de una rutinago entrando en pánico. Supongamos que una función G difiere una función D que llama a recover y se produce pánico en una función en la misma rutinago en la que se está ejecutando G. Cuando el funcionamiento natural de las funciones diferidas alcanza a D, el valor de retorno de D en la llamada a recover será el valor que se pase a la llamada de panic. Si D regresa normalmente, sin necesidad de iniciar un nuevo pánico, la secuencia de pánico se detiene. En ese caso, el estado de las funciones llamadas entre G y la llamada a pánico se descartan y se reanuda la ejecución normal. Cualquier función diferida por G antes de D entonces se ejecutan y la ejecución de G termina volviendo a su llamador.

El valor de retorno de recover es nil si se cumple alguna de las siguientes condiciones:

  • el argumento para panic era nil;
  • la rutinago no es presa del pánico;
  • recover no fue llamado directamente por una función diferida.

La función protege en el siguiente ejemplo invoca al argumento de la función g y protege a los llamadores de pánico en tiempo de ejecución planteados por g.

func protege(g func()) {
    defer func() {
        log.Println("hecho")  // Println ejecuta normalmente incluso
                              // si hay un pánico
        if x := recover(); x != nil {
            log.Printf("pánico en tiempo de ejecución: %v", x)
        }
    }()
    log.Println("inicio")
    g()
}

Proceso de arranque

Las implementaciones actuales proporcionan varias funciones incorporadas útiles durante el proceso de arranque (bootstrapping en inglés). Estas funciones están completamente documentadas pero no se garantiza que permanezcan en el lenguaje. No devuelven un resultado.

Función    Comportamiento
print      imprime todos los argumentos; el formato de los argumentos es
           específico de la implementación
println    como print pero imprime espacios entre argumentos y una nueva
           línea al final

Paquetes

Los programas Go se construyen enlazando paquetes. Un paquete a su vez, se construye a partir de uno o más archivos fuente que juntos declaran constantes, tipos, variables y funciones propias del paquete y que son accesibles en todos los archivos del mismo paquete. Estos elementos se pueden exportar y utilizar en otros paquetes.

Organización de archivos fuente

Cada archivo fuente consta de una cláusula package que define el paquete al que pertenece, seguida de una serie posiblemente vacía de declaraciones de importación que declaran los paquetes que desea utilizar, seguida de una serie posiblemente vacía de declaraciones de constantes, tipos, variables y funciones.

ArchivoFuente    = CláusulaPackage ";" { InstruImport ";" } { DeclaNivelSuperior ";" } .

Cláusula package

Una cláusula package comienza cada archivo fuente y define el paquete al que pertenece el archivo.

CláusulaPackage  = "package" NombrePaquete .
NombrePaquete    = identificador .

El NombrePaquete no debe ser el identificador blanco.

package math

Un conjunto de archivos que comparten el mismo NombrePaquete forman la implementación de un paquete. Una implementación puede requerir que todos los archivos fuente para un paquete habiten en el mismo directorio.

Instrucciones import

Una instrucción import indica que el archivo fuente conteniendo la declaración depende de la funcionalidad de la importación del paquete importado (inicio y ejecución del programa) y permite el acceso a los identificadores exportados de ese paquete. La import nombra un identificador (NombrePaquete) que se utilizará para acceder y una RutaImportación que especifica el paquete a ser importado.

InstruImport       = "import" ( EspecImport | "(" { EspecImport ";" } ")" ) .
EspecImport        = [ "." | NombrePaquete ] RutaImportación .
RutaImportación    = cadena_lit .

El NombrePaquete se utiliza en identificadores cualificados para acceder a los identificadores exportados del paquete dentro del archivo fuente importado. Se declara en el bloque de archivo. Si se omite el NombrePaquete, toma como predeterminado el identificador especificado en la cláusula package del paquete importado. Si aparece un punto explícito (.) en lugar de un nombre, todos los identificadores exportados del paquete declarados en ese bloque de paquete serán declarados en el bloque de archivo del archivo fuente importador y se debe acceder a ellos sin un calificador.

La interpretación de la RutaImportación depende de la implementación, pero típicamente es una subcadena del nombre de archivo completo del paquete compilado y puede ser relativa a un repositorio de paquetes instalados.

Restricción de implementación: Un compilador puede restringir las RutaImportación a cadenas no vacías utilizando sólo caracteres pertenecientes a Unicode categorías generales L, M, N, P y S (los caracteres gráficos sin espacios) y también puede excluir los caracteres !&quot;#$%&amp;&#39;()*,:;&lt;=&gt;?[\]^{|}` y el carácter de reemplazo Unicode U+FFFD.

Supongamos que hemos compilado un paquete que contiene la cláusula de paquete package math, que exporta la función Sin y el paquete compilado está instalado en el archivo identificado por &quot;lib/math&quot;. Esta tabla demuestra cómo se accede a Sin en los archivos que importan el paquete después de los distintos tipos de declaración de importación.

Declaración de importación    Nombre local de Sin
import   "lib/math"           math.Sin
import m "lib/math"           m.Sin
import . "lib/math"           Sin

Una declaración de importación declara una relación de dependencia entre el importador y el paquete importado. Es ilegal que un paquete directa o indirectamente se importe a sí mismo o importar directamente un paquete sin hacer referencia a cualquiera de sus identificadores exportados. Para importar un paquete únicamente por sus efectos secundarios (iniciación), utiliza el identificador blanco como nombre de paquete explícito:

import _ "lib/math"

Un paquete de ejemplo

Aquí tienes un paquete Go completo que implementa un tamiz primo concurrente.

package main

import "fmt"

// Envía la secuencia 2, 3, 4, … al canal 'ch'.
func genera(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i  // Envía 'i' al canal 'ch'.
    }
}

// Copia los valores del canal «src» al canal 'dst',
// quitando los divisibles por 'primo'.
func filtra(src <-chan int, dst chan<- int, primo int) {
    for i: = range src { // Bucle sobre los valores recibidos de «src».
        if i%primo != 0 {
            dst <- i  // Envía 'i' al canal 'dst'.
        }
    }
}

// El tamiz primo: filtra la conexión en cadena y los procesa juntos.
func tamiz() {
    ch := make(chan int)  // Crea un nuevo canal.

    go genera(ch)       // Inicia genera() como un subproceso.
    for {
        primo := <-ch
        fmt.Print(primo, "\n")
        ch1 := make(chan int)
        go filtra(ch, ch1, primo)
        ch = ch1
    }
}

func main() {
    tamiz()
}

Iniciación y ejecución de programa

El valor cero

Cuando se asigna almacenamiento a una variable, ya sea a través de una declaración o una llamada a new o cuando se crea un nuevo valor, bien a través de un literal compuesto o por medio de una llamada a make y no se proporciona iniciación explícita, se da a la variable o valor un valor predeterminado. Cada elemento de una variable o valor tal se establece al valor cero para su tipo: false para lógicos, 0 para números enteros, 0.0 para número de coma flotante, &quot;&quot; para cadenas y nil para punteros, funciones, interfaces, sectores, canales y mapas. Esta iniciación se realiza de forma recursiva, por lo que, por ejemplo, cada elemento de un arreglo de estructuras tendrá sus campos a cero si no se especifica ningún valor.

Estas dos simples declaraciones son equivalentes:

var i int
var i int = 0

Después de:

type T struct { i int; f float64; siguiente *T }

t := new(T)

se cumple lo siguiente:

t.i == 0
t.f == 0.0
t.siguiente == nil

Lo mismo sería cierto después de:

var t T

Iniciación del paquete

Dentro de un paquete, las variables a nivel de paquete se inician en orden de declaración pero después de cualquiera de las variables de que dependen.

Específicamente, una variable a nivel de paquete se considera lista para iniciación si aún no se ha iniciado y, o bien no tiene ninguna expresión de iniciación o su expresión de iniciación no tiene ninguna dependencia de variables sin iniciar. La iniciación procede por iniciar repetidamente la siguiente variable a nivel de paquete que se declaró más temprano y está lista para iniciación, hasta que no haya más variables listas para iniciación.

Si algunas variables están todavía sin iniciar cuando termine este proceso, esas variables son parte de uno o más ciclos de iniciación y el programa no es válido.

El orden de la declaración de variables declaradas en varios archivos es determinado por el orden en que los archivos se presentan al compilador: Las variables declaradas en el primer archivo se declaran antes de cualquiera de las variables declaradas en el segundo archivo y así sucesivamente.

El análisis de dependencia no se basa en los valores reales de las variables, sólo en referencias léxicas a ellos en la fuente, analizados transitivamente. Por ejemplo, si la expresión de iniciación para una variable x se refiere a una función cuyo cuerpo se refiere a la variable y entonces x depende de y. Específicamente:

  • Una referencia a una variable o función es un identificador que denota esa variable o función.
  • Una referencia a un método m es un valor método o expresión método de la forma t.m, donde el tipo (estático) de t no es un tipo interfaz y el método m está en el conjunto de métodos de t. Es irrelevante que el valor resultante de la función t.m sea invocado.
  • Una variable, función o método x depende de una variable y si la expresión de iniciación de x o el cuerpo (para funciones y métodos) contiene una referencia a y o a una función o método que dependa de y.

El análisis de dependencias se realiza por paquete; únicamente se consideran referencias a variables, funciones y métodos declarados en el paquete actual.

Por ejemplo, dadas las declaraciones:

var (
    a = c + b
    b = f()
    c = f()
    d = 3
)

func f() int {
    d++
    return d
}

el orden de iniciación es d, b, c, a.

Las variables también se pueden iniciar utilizando funciones con nombre init declaradas en el bloque del paquete, sin argumentos y sin parámetros de resultado.

func init() { … }

Se pueden definir múltiples funciones como esta, incluso dentro de un solo archivo fuente. El identificador init no está declarado y por lo tanto las funciones init no se pueden referir desde cualquier parte de un programa.

Un paquete sin importaciones se inicia mediante la asignación de valores iniciales a todas sus variables a nivel de paquete seguida por una llamada a todas las funciones init en el orden en que aparecen en la fuente, posiblemente en varios archivos, tal como se presentan al compilador. Si un paquete tiene importaciones, los paquetes importados se inician antes de iniciar el propio paquete. Si varios paquetes importan un paquete, el paquete importado se iniciará únicamente una vez. La importación de paquetes, por construcción, garantiza que no puede haber ninguna dependencia de iniciación cíclica.

La iniciación de variables del paquete y la invocación a las funciones init ocurre secuencialmente en una sola rutinago, un paquete a la vez. Una función init puede lanzar otras rutinagos, mismas que se pueden ejecutar simultáneamente con el código de iniciación. Sin embargo, la iniciación siempre secuencia las funciones init: no invocará a la siguiente hasta que la anterior haya regresado.

Para garantizar un comportamiento de iniciación reproducible, se anima a construir sistemas para presentar varios archivos pertenecientes a un mismo paquete con el fin de nombrar archivo léxicos a un compilador.

Ejecución del programa

Un programa completo se crea enlazando un solo paquete no importado llamado el package main con todos los paquetes que importa, transitivamente. El paquete principal debe tener el nombre de paquete main y declarar una función main que no tiene argumentos y no devuelve nada.

func main() { … }

La ejecución del programa comienza iniciando el paquete principal y luego invocando a la función main. Cuando esa invocación a función regresa, el programa se cierra. No espera a que otras rutinasgo (no main) terminen.

Errores

El tipo predeclarado error se define como:

type error interface {
    Error() string
}

Es la interfaz convencional para representar una condición de error, el valor nil representa la ausencia de errores. Por ejemplo, una función para leer datos de un archivo se puede definir así:

func Lee(f *File, b []byte) (n int, err error)

Pánicos en tiempo de ejecución

Errores de ejecución como el intento de indexar un arreglo fuera de límites desencadena un pánico en tiempo de ejecución equivalente a llamar a la función incorporada panic con un valor de tipo interfaz runtime.Error definido en la implementación. Ese tipo se ajusta al tipo interfaz error predeclarado. Los valores de error exactos que representan condiciones de error en distintos tiempo de ejecución no están especificados.

package runtime

type Error interface {
    error
    // y tal vez otros métodos
}

Consideraciones del sistema

Paquete unsafe

El paquete integrado unsafe, conocido por el compilador, ofrece facilidades para la programación de bajo nivel incluyendo las operaciones que violan el sistema de tipos. Un paquete usando unsafe debe revisar manualmente la seguridad de tipos y puede no ser portátil. El paquete proporciona la siguiente interfaz:

package unsafe

type ArbitraryType int  // abreviatura de un tipo Go arbitrario; no es
                        // un tipo real

type Pointer *ArbitraryType

func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr

Un Pointer es un tipo puntero pero un valor Pointer no se puede desreferenciar. Cualquier puntero o valor del tipo subyacente uintptr se puede convertir a un tipo Pointer y viceversa. El efecto de convertir entre Pointer y uintptr está definido por la implementación.

var f float64

bits = *(*uint64)(unsafe.Pointer(&f))
type ptr unsafe.Pointer

bits = *(*uint64)(ptr(&f))
var p ptr = nil

Las funciones Alignof y Sizeof toman una expresión x de cualquier tipo y devuelven la alineación o tamaño, respectivamente, de una hipotética variable v como si v hubiese sido declarada a través de var v = x.

La función Offsetof toma un (posiblemente entre paréntesis) selector s.f, que denota un campo s de la estructura denotada por s o *s y devuelve el desplazamiento del campo en bytes relativo a la estructura dirección. Si f es un campo incrustado, este debe ser accesible sin indirecciones de puntero a través de los campos de la estructura. Para una estructura s con el campo f:

uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))

La arquitectura de las computadoras puede requerir que las direcciones de memoria estén alineadas; es decir, que las direcciones de una variable sean un múltiplo de un factor, el tipo alineación de la variable. La función Alignof toma una expresión que denota una variable de cualquier tipo y devuelve la alineación (del tipo) de la variable en bytes. Para una variable x:

uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0

Las llamadas a Alignof, Offsetof y Sizeof son expresiones constantes de tiempo de compilación de tipo uintptr.

Garantías de tamaño y alineación

Para los tipos numéricos, los siguientes tamaños están garantizados:

tipo                                 tamaño en bytes
byte, uint8, int8                     1
uint16, int16                         2
uint32, int32, float32                4
uint64, int64, float64, complex64     8
complex128                           16

Las siguientes propiedades mínimas de alineación están garantizadas:

  1. Para una variable x de cualquier tipo: unsafe.Alignof(x) es al menos 1.
  2. Para una variable x de tipo estructura: unsafe.Alignof(x) es el más grande de todos los valores unsafe.Alignof(x.f) para cada campo f de x, pero al menos 1.
  3. Para una variable x de tipo arreglo: unsafe.Alignof(x) es el mismo que unsafe.Alignof(x[0]), pero por lo menos 1.

Una estructura o tipo arreglo tiene tamaño cero si no contiene campos (o elementos, respectivamente) que tengan un tamaño mayor que cero. Dos distintas variables de tamaño cero pueden tener la misma dirección en memoria.

En su mayor parte este libro se reproduce a partir del trabajo creado y compartido por Google traducido al Español y se usa de acuerdo a los términos descritos en la Licencia Creative Commons 3.0 Attribution.