Vamos | |
---|---|
clase de idioma | multiproceso , imperativo , estructurado , orientado a objetos [1] [2] |
tipo de ejecución | compilado |
Apareció en | 10 de noviembre de 2009 |
Autor | Robert Grismer , Rob Pike y Ken Thompson |
Desarrollador | Google , Rob Pike , Ken Thompson , The Go Authors [d] y Robert Grismer [d] |
extensión de archivo | .go |
Liberar |
|
sistema de tipos | estricto , estático , con inferencia de tipo |
sido influenciado | C [4] , Oberon-2 , Limbo , Active Oberon , Teoría de la interacción del proceso secuencial , Pascal [4] , Oberon [4] , Smalltalk [5] , Newsqueak [d] [6] , Modula-2 [6] , Alef [d] , APL [7] , BCPL , Modula y Occam |
Licencia | BSD |
Sitio web | ir.dev _ |
sistema operativo | DragonFly BSD , FreeBSD , Linux , macOS , NetBSD , OpenBSD , Plan 9 , Solaris , Microsoft Windows , iOS , Android , AIX e Illumos |
Archivos multimedia en Wikimedia Commons |
Go (a menudo también golang ) es un lenguaje de programación compilado de subprocesos múltiples desarrollado internamente por Google [8] . El desarrollo de Go comenzó en septiembre de 2007, con Robert Grismer , Rob Pike y Ken Thompson [9] , quienes trabajaron anteriormente en el proyecto de desarrollo del sistema operativo Inferno , directamente involucrados en su diseño . El idioma se introdujo oficialmente en noviembre de 2009 . Por el momento, se brinda soporte para el compilador oficial desarrollado por los creadores del lenguaje para los sistemas operativos FreeBSD , OpenBSD , Linux , macOS , Windows , DragonFly BSD , Plan 9 , Solaris , Android , AIX . [10] . Go también es compatible con el conjunto de compiladores gcc y existen varias implementaciones independientes. Se está desarrollando una segunda versión del lenguaje.
El nombre del lenguaje elegido por Google es casi el mismo que el nombre del lenguaje de programación Go! , creado por F. Gee. McCabe y CL Clark en 2003 [11] . El nombre se discute en la página Ir [11] .
En la página de inicio del idioma y, en general, en las publicaciones de Internet, a menudo se usa el nombre alternativo "golang".
El lenguaje Go se desarrolló como un lenguaje de programación para crear programas altamente eficientes que se ejecutan en sistemas distribuidos modernos y procesadores multinúcleo. Puede verse como un intento de crear un reemplazo para los lenguajes C y C++ , teniendo en cuenta las nuevas tecnologías informáticas y la experiencia acumulada en el desarrollo de grandes sistemas [12] . En palabras de Rob Pike [12] , "Go fue diseñado para resolver problemas de desarrollo de software de la vida real en Google". Él enumera los siguientes como los principales problemas:
Los principales requisitos para el idioma fueron [13] :
Go se creó con la expectativa de que los programas en él se tradujeran a código objeto y se ejecutaran directamente sin necesidad de una máquina virtual , por lo que uno de los criterios para elegir soluciones arquitectónicas fue la capacidad de garantizar una compilación rápida en código objeto eficiente y la ausencia de requisitos para el apoyo dinámico.
El resultado fue un lenguaje "que no fue un gran avance, pero sin embargo fue una excelente herramienta para el desarrollo de grandes proyectos de software" [12] .
Aunque se dispone de un intérprete para Go , prácticamente no hay gran necesidad del mismo, ya que la velocidad de compilación es lo suficientemente rápida como para permitir el desarrollo interactivo.
Las principales características del lenguaje Go [9] :
Go no incluye muchas de las características sintácticas populares disponibles en otros lenguajes de programación de aplicaciones modernos. En muchos casos, esto se debe a una decisión consciente de los desarrolladores. Se pueden encontrar breves justificaciones para las decisiones de diseño elegidas en las "Preguntas frecuentes" [9] sobre el idioma, más detalladas, en los artículos y discusiones publicados en el sitio del idioma, considerando varias opciones de diseño. En particular:
La sintaxis del lenguaje Go es similar a la del lenguaje C , con elementos tomados de Oberon y lenguajes de programación .
Go es un lenguaje que distingue entre mayúsculas y minúsculas con compatibilidad total con Unicode para cadenas e identificadores.
Tradicionalmente, un identificador puede ser cualquier secuencia no vacía de letras, números y un guión bajo que comienza con una letra y no coincide con ninguna de las palabras clave de Go. "Letras" hace referencia a todos los caracteres Unicode que se incluyen en las categorías "Lu" (letras mayúsculas), "Ll" (letras minúsculas), "Lt" (letras mayúsculas), "Lm" (letras modificadoras) o "Lo" ( otras letras), en "números": todos los caracteres de la categoría "Nd" (números, dígitos decimales). Así, nada impide utilizar cirílico en identificadores, por ejemplo.
Los identificadores que difieren solo en mayúsculas y minúsculas son distintos. El lenguaje tiene una serie de convenciones para el uso de letras mayúsculas y minúsculas. En particular, solo se usan letras minúsculas en los nombres de los paquetes. Todas las palabras clave de Go se escriben en minúsculas. Las variables que comienzan con letras mayúsculas son exportables (públicas) y las que comienzan con letras minúsculas no son exportables (privadas).
Los literales de cadena pueden usar todos los caracteres Unicode sin restricciones. Las cadenas se representan como secuencias de caracteres UTF-8 .
Cualquier programa Go incluye uno o más paquetes. El paquete al que pertenece un archivo de código fuente viene dado por la descripción del paquete al principio del archivo. Los nombres de los paquetes tienen las mismas restricciones que los identificadores, pero solo pueden contener letras minúsculas. El sistema de paquetes goroutine tiene una estructura de árbol similar a un árbol de directorios. Todos los objetos globales (variables, tipos, interfaces, funciones, métodos, elementos de estructuras e interfaces) están disponibles sin restricciones en el paquete en el que se declaran. Los objetos globales cuyos nombres comienzan con una letra mayúscula son exportables.
Para usar objetos exportados por otro paquete en un archivo de código Go, el paquete debe importarse con la extensión import.
paquete principal /* Importar */ importar ( "fmt" // Paquete estándar para salida formateada "database/sql" // Importar paquete anidado w "os" // Importar con alias . "math" // Importar sin calificar cuando se usa _ "gopkg.in/goracle.v2" // El paquete no tiene referencias explícitas en el código ) func principal () { para _ , arg := rango w . Args { // Acceso a la matriz Args declarada en el paquete "os" a través del alias fmt . Println ( arg ) // Llamar a la función Println() declarada en el paquete "fmt" con el nombre del paquete } var db * sql . db = sql . Open ( driver , dataSource ) // Los nombres del paquete anidado se califican // solo por el nombre del paquete en sí (sql) x := Sin ( 1.0 ) // llame a math.Sin() - calificación por el nombre del paquete math no es necesario // porque se importó sin nombre // No hay ninguna referencia al paquete "goracle.v2" en el código, pero se importará. }Enumera las rutas a los paquetes importados desde el directorio src en el árbol de origen, cuya posición viene dada por la variable de entorno GOPATH, mientras que para los paquetes estándar, solo especifique el nombre. Una cadena que identifica un paquete puede ir precedida de un alias, en cuyo caso se usará en el código en lugar del nombre del paquete. Los objetos importados están disponibles en el archivo que los importa con una calificación completa como " пакет.Объект". Si un paquete se importa con un punto en lugar de un alias, todos los nombres que exporte estarán disponibles sin calificación. Algunas utilidades del sistema utilizan esta función, pero no se recomienda su uso por parte del programador, ya que la calificación explícita brinda protección contra colisiones de nombres y cambios "imperceptibles" en el comportamiento del código. No es posible importar sin calificación dos paquetes exportando el mismo nombre.
La importación de paquetes en Go está estrictamente controlada: si un módulo importa un paquete, al menos un nombre exportado por ese paquete debe usarse en el código de ese módulo. El compilador de Go trata la importación de un paquete no utilizado como un error; esta solución obliga al desarrollador a mantener constantemente actualizadas las listas de importación. Esto no crea ninguna dificultad, ya que las herramientas de soporte de programación de Go (editores, IDE) generalmente brindan verificación y actualización automáticas de las listas de importación.
Cuando un paquete contiene código que se usa solo a través de la introspección , hay un problema: la importación de dicho paquete es necesaria para incluirlo en el programa, pero el compilador no lo permitirá, ya que no se accede directamente. _Se proporciona importación anónima para tales casos: “ ” (guión bajo único) se especifica como un alias ; un paquete importado de esta manera se compilará e incluirá en el programa si no se hace referencia explícita a él en el código. Sin embargo, dicho paquete no se puede usar explícitamente; esto evita que se omita el control de importación al importar todos los paquetes como anónimos.
Un programa Go ejecutable debe contener un paquete llamado main, que debe contener una función main()sin parámetros y un valor de retorno. La función main.main()es el "cuerpo del programa": su código se ejecuta cuando se inicia el programa. Cualquier paquete puede contener una función init() : se ejecutará cuando se cargue el programa, antes de que comience a ejecutarse, antes de llamar a cualquier función en este paquete y en cualquier paquete que lo importe. El paquete principal siempre se inicializa en último lugar y todas las inicializaciones se realizan antes de que la función comience a ejecutarse main.main().
El sistema de empaquetado de Go se diseñó asumiendo que todo el ecosistema de desarrollo existe como un único árbol de archivos que contiene versiones actualizadas de todos los paquetes, y cuando aparecen nuevas versiones, se vuelve a compilar por completo. Para la programación de aplicaciones que utilizan bibliotecas de terceros, esta es una limitación bastante fuerte. En realidad, a menudo hay restricciones en las versiones de los paquetes utilizados por uno u otro código, así como situaciones en las que diferentes versiones (ramas) de un proyecto utilizan diferentes versiones de paquetes de biblioteca.
Desde la versión 1.11, Go admite los llamados módulos . Un módulo es un paquete especialmente descrito que contiene información sobre su versión. Cuando se importa un módulo, se fija la versión que se utilizó. Esto permite que el sistema de compilación controle si se cumplen todas las dependencias, actualice automáticamente los módulos importados cuando el autor realice cambios compatibles en ellos y bloquee las actualizaciones de versiones no compatibles con versiones anteriores. Se supone que los módulos son una solución (o una solución mucho más fácil) al problema de la gestión de dependencias.
Go usa ambos tipos de comentarios de estilo C: comentarios en línea (que comienzan con // ...) y comentarios en bloque (/* ... */). El compilador trata un comentario de línea como una nueva línea. Bloque, ubicado en una línea, como un espacio, en varias líneas, como una nueva línea.
El punto y coma en Go se usa como separador obligatorio en algunas operaciones (if, for, switch). Formalmente, también debería finalizar cada comando, pero en la práctica no es necesario poner ese punto y coma al final de la línea, ya que el compilador mismo agrega punto y coma al final de cada línea, excluyendo los caracteres vacíos, al final del identificador, número, un carácter literal, una cadena, las palabras clave de interrupción, continuar, fallar, devolver, un comando de incremento o decremento (++ o --), o un paréntesis de cierre, un cuadrado o una llave (una excepción importante es que una coma no está incluida en la lista anterior). De esto se siguen dos cosas:
El lenguaje contiene un conjunto bastante estándar de tipos de datos integrados simples: enteros, números de coma flotante, caracteres, cadenas, booleanos y algunos tipos especiales.
EnterosHay 11 tipos de enteros:
Los creadores del lenguaje recomiendan usar solo el tipo estándar para trabajar con números dentro del programa int. Los tipos con tamaños fijos están diseñados para trabajar con datos recibidos o pasados a fuentes externas, cuando es importante para la corrección del código especificar un tamaño específico del tipo. Los tipos son sinónimos bytey runeestán diseñados para trabajar con datos y símbolos binarios, respectivamente. El tipo uintptres necesario solo para la interacción con código externo, por ejemplo, en C.
Números de punto flotanteLos números de coma flotante están representados por dos tipos, float32y float64. Su tamaño es de 32 y 64 bits, respectivamente, la implementación cumple con el estándar IEEE 754 . El rango de valores se puede obtener del paquete estándar math.
Tipos numéricos con precisión ilimitadaLa biblioteca estándar de Go también contiene el paquete big, que proporciona tres tipos con precisión ilimitada: big.Int, big.Raty big.Float, que representan números enteros, racionales y de punto flotante, respectivamente; el tamaño de estos números puede ser cualquiera y está limitado únicamente por la cantidad de memoria disponible. Dado que los operadores en Go no están sobrecargados, las operaciones computacionales sobre números con precisión ilimitada se implementan como métodos ordinarios. El rendimiento de los cálculos con números grandes, por supuesto, es significativamente inferior a los tipos numéricos incorporados, pero al resolver ciertos tipos de problemas computacionales, bigpuede ser preferible usar un paquete que optimizar manualmente un algoritmo matemático.
Números complejosEl lenguaje también proporciona dos tipos integrados para números complejos complex64y complex128. Cada valor de estos tipos contiene un par de partes reales e imaginarias que tienen tipos, respectivamente, float32y float64. Puede crear un valor de un tipo complejo en el código de una de dos formas: mediante una función integrada complex()o mediante un literal imaginario en una expresión. Puedes obtener las partes real e imaginaria de un número complejo usando las funciones real()y imag().
var x complex128 = complex ( 1 , 2 ) // 1 + 2i y := 3 + 4i // 3 + 4i , siendo 4 un número seguido de un sufijo i // es un literal fmt imaginario . Println ( x * y ) // imprime "(-5+10i)" fmt . Println ( real ( x * y )) // imprime "-5" fmt . Println ( imagen ( x * y )) // imprime "10" Valores booleanosEl tipo booleano booles bastante común: incluye los valores predefinidos truey falsedenota, respectivamente, verdadero y falso. A diferencia de C, los valores booleanos en Go no son numéricos y no se pueden convertir directamente en números.
CuerdasLos valores de tipo cadena stringson matrices de bytes inmutables que contienen UTF-8. Esto provoca una serie de características específicas de las cadenas (por ejemplo, en el caso general, la longitud de una cadena no es igual a la longitud de la matriz que la representa, es decir, el número de caracteres que contiene no es igual al número de bytes en la matriz correspondiente). Para la mayoría de las aplicaciones que procesan cadenas completas, esta especificidad no es importante, pero en los casos en que el programa debe procesar directamente runas específicas (caracteres Unicode), unicode/utf8se requiere un paquete que contenga herramientas auxiliares para trabajar con cadenas Unicode.
Para cualquier tipo de datos, incluidos los incorporados, se pueden declarar nuevos tipos analógicos que repiten todas las propiedades de los originales, pero son incompatibles con ellos. Estos nuevos tipos también pueden declarar métodos opcionalmente. Los tipos de datos definidos por el usuario en Go son punteros (declarados con el símbolo *), matrices (declaradas con corchetes), estructuras ( struct), funciones ( func), interfaces ( interface), asignaciones ( map) y canales ( chan). Las declaraciones de estos tipos especifican los tipos y posiblemente los identificadores de sus elementos. Los nuevos tipos se declaran con la palabra clave type:
escriba PostString cadena // Escriba "cadena", similar a incorporado type StringArray [] string // Tipo de matriz con elementos de tipo cadena type Person struct { // Struct type name string // campo de la cadena estándar type post PostString // campo de la cadena personalizada previamente declarada type bdate time . Time // campo de tipo Time, importado del paquete time edate time . Jefe de tiempo * Persona // campo de puntero inferir []( * Persona ) // campo de matriz } type InOutString chan string // tipo de canal para pasar cadenas type CompareFunc func ( a , b interface {}) int // tipo de función.Desde la versión Go 1.9, también está disponible la declaración de alias de tipo (alias):
type TitleString = string // "TitleString" es un alias para el tipo integrado string type Integer = int64 // "Integer" es un alias para el tipo integrado de 64 bitsSe puede declarar un alias para un tipo de sistema o para cualquier tipo definido por el usuario. La diferencia fundamental entre los alias y las declaraciones de tipo ordinario es que la declaración crea un nuevo tipo que no es compatible con el original, incluso si no se agregan cambios al tipo original en la declaración. Un alias es solo otro nombre para el mismo tipo, lo que significa que el alias y el tipo original son completamente intercambiables.
Los campos de estructura pueden tener etiquetas en la descripción: secuencias arbitrarias de caracteres entre comillas posteriores:
// Estructura con etiquetas de campo tipo XMLInvoices struct { XMLName xml . Nombre `xml:"FACTURAS"` Versión int `xml:"version,attr"` Factura [] * XMLInvoice `xml:"FACTURA"` }El compilador ignora las etiquetas, pero la información sobre ellas se coloca en el código y se puede leer usando las funciones del paquete reflectincluido en la biblioteca estándar. Por lo general, las etiquetas se utilizan para proporcionar clasificación de tipos para almacenar y restaurar datos en medios externos o interactuar con sistemas externos que reciben o transmiten datos en sus propios formatos. El ejemplo anterior utiliza etiquetas procesadas por la biblioteca estándar para leer y escribir datos en formato XML.
La sintaxis para declarar variables se resuelve principalmente en el espíritu de Pascal: la declaración comienza con la palabra clave var, seguida del nombre de la variable a través del separador, luego, a través del separador, su tipo.
Vamos | C++ |
---|---|
var v1 int const v2 string var v3 [ 10 ] int var v4 [] int var v5 struct { f int } var v6 * int /* aritmética de punteros no admitida */ var v7 map [ string ] int var v8 func ( a int ) En t | intv1 ; _ constante estándar :: stringv2 ; _ /* sobre */ intv3 [ 10 ] ; int * v4 ; /* sobre */ estructura { int f ; } v5 ; int * v6 ; std :: unordered_map v7 ; /* sobre */ int ( * v8 )( int a ); |
La declaración de variables se puede combinar con la inicialización:
var v1 int = 100 var v2 cadena = "¡Hola!" var v3 [ 10 ] int = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } var v4 [] int = { 1000 , 2000 , 12334 } var v5 struct { f int } = { 50 } var v6 * int = & v1 var v7 mapa [ cadena ] int = { "uno" : 1 , "dos" : 2 , "tres" : 3 } var v8 func ( a int ) int = func ( a int ) int { devuelve un + 1 }Si una variable no se inicializa explícitamente al declararla , entonces se inicializa automáticamente a "valor nulo" para el tipo dado. El valor nulo para todos los tipos numéricos es 0, para un tipo string es la cadena vacía, para los punteros es nil. Las estructuras se inicializan por defecto con conjuntos de valores cero para cada uno de los campos incluidos en ellas, los elementos del array se inicializan con valores cero del tipo especificado en la definición del array.
Los anuncios se pueden agrupar:
var ( yo int m flotante )El lenguaje Go también admite la inferencia automática de tipos . Si una variable se inicializa cuando se declara, se puede omitir su tipo: el tipo de la expresión que se le asigna se convierte en el tipo de la variable. Para los literales (números, caracteres, cadenas), el estándar del lenguaje define tipos integrados específicos a los que pertenece cada valor. Para inicializar una variable de otro tipo, se debe aplicar una conversión de tipo explícita al literal.
var p1 = 20 // p1 int - el literal entero 20 es de tipo int. var p2 = uint ( 20 ) // p2 uint - valor convertido explícitamente a uint. var v1 = & p1 // v1 *int es un puntero a p1, para el cual se deduce el tipo int. var v2 = & p2 // v2 *uint es un puntero a p2, que se inicializa explícitamente como un entero sin signo.Para las variables locales, existe una forma abreviada de declaración combinada con la inicialización mediante inferencia de tipos:
v1 := 100 v2 := "¡Hola!" v3 := [ 10 ] int { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } v4 := [] int { 1000 , 2000 , 12334 } v5 := estructura { f int }{ 50 } v6 := & v1Go usa el símbolo como operador de asignación =:
a = b // Establecer variable a a bComo se mencionó anteriormente, existe una forma de definir una variable con inferencia de tipo automática combinada con inicialización, que se asemeja externamente a la asignación en Pascal :
v1 := v2 // similar a var v1 = v2El compilador de Go realiza un seguimiento estricto de las definiciones y asignaciones y distingue unas de otras. Dado que la redefinición de una variable con el mismo nombre está prohibida en un ámbito, dentro de un bloque de código, una variable puede aparecer a la izquierda del signo :=solo una vez:
a := 10 // Declarar e inicializar una variable entera a. b := 20 // Declarar e inicializar una variable entera b. ... a := b // ¡ERROR! Intento de redefinir a.Go permite ejecutar varias asignaciones en paralelo:
i , j = j , i // Intercambiar valores de i y j.En este caso, el número de variables a la izquierda del signo de asignación debe coincidir exactamente con el número de expresiones a la derecha del signo de asignación.
La asignación en paralelo también es posible cuando se utiliza el :=. Su peculiaridad es que entre las variables enumeradas a la izquierda del signo :=, puede haber otras ya existentes. En este caso, se crearán nuevas variables y se reutilizarán las existentes. Esta sintaxis se usa a menudo para el manejo de errores:
x , err := SomeFunction () // La función devuelve dos valores (ver más abajo), // se declaran e inicializan dos variables. if ( err != nil ) { return nil } y , err := SomeOtherFunction () // Aquí solo se declara y, a err simplemente se le asigna un valor.En la última línea del ejemplo, el primer valor devuelto por la función se asigna a la nueva variable y, el segundo a la variable ya existente err, que se utiliza a lo largo del código para colocar el último error devuelto por las funciones llamadas. Si no fuera por esta característica del operador :=, en el segundo caso habría que declarar una nueva variable (por ejemplo, err2) o declarar por separado yy luego usar la asignación paralela habitual.
Go implementa la semántica de "copia en asignación", lo que significa que una asignación da como resultado hacer una copia del valor de la variable original y colocar esa copia en otra variable, después de lo cual los valores de las variables son diferentes y cambia uno de ellos no cambia el otro. Sin embargo, esto solo es cierto para tipos, estructuras y matrices escalares integrados con una longitud determinada (es decir, tipos cuyos valores se asignan en la pila). Las matrices de longitud indefinida y las asignaciones se asignan en el montón , las variables de este tipo en realidad contienen referencias a objetos, cuando se asignan, solo se copia la referencia, pero no el objeto en sí. A veces esto puede conducir a efectos inesperados. Considere dos ejemplos casi idénticos:
type vector [ 2 ] float64 // La longitud del arreglo se establece explícitamente v1 := vector { 10 , 15.5 } // Inicialización - v1 contiene el arreglo mismo v2 := v1 // El arreglo v1 se copia al arreglo v2 v2 [ 0 ] = 25.3 // Cambiado solo la matriz v2 fmt . Println ( v1 ) // Imprime "[10 15.5]" - la matriz original no ha cambiado. fmt . Println ( v2 ) // Imprime "[25.3 15.5]"Aquí el tipo se vectordefine como una matriz de dos números. La asignación de dichas matrices se comporta de la misma manera que la asignación de números y estructuras.
Y en el siguiente ejemplo, el código difiere exactamente en un carácter: el tipo vectorse define como una matriz con un tamaño indefinido. Pero este código se comporta de manera completamente diferente:
type vector [] float64 // Matriz con longitud indefinida v1 := vector { 10 , 15.5 } // Inicialización - v1 contiene referencia de matriz v2 := v1 // La referencia de matriz se copia de v1 a v2 v2 [ 0 ] = 25.3 // Se puede pensar en cambiar solo la matriz v2 fmt . Println ( v1 ) // Imprime "[25.3 15.5]" - ¡la matriz original CAMBIÓ! fmt . Println ( v2 ) // Imprime "[25.3 15.5]"De la misma manera que en el segundo ejemplo, se comportan las asignaciones y las interfaces. Además, si la estructura tiene un campo de tipo referencia o interfaz, o un campo es una matriz o mapeo adimensional, entonces al asignar dicha estructura, también se copiará solo la referencia, es decir, los campos de diferentes estructuras comenzarán para apuntar a los mismos objetos en la memoria.
Para evitar este efecto, debe utilizar explícitamente una función del sistema copy()que garantice la creación de una segunda instancia del objeto.
se declaran así:
func f ( yo , j , k int , s , t cadena ) cadena { }Los tipos de tales valores están encerrados entre paréntesis:
func f ( a , b int ) ( int , string ) { return a + b , "adición" }Los resultados de la función también se pueden nombrar:
func incTwo ( a , b int ) ( c , d int ) { c = a + 1 d = b + 1 volver }Los resultados con nombre se consideran declarados inmediatamente después del encabezado de la función con cero valores iniciales. La declaración de retorno en dicha función se puede usar sin parámetros, en cuyo caso, después de regresar de la función, los resultados tendrán los valores que se les asignaron durante su ejecución. Entonces, en el ejemplo anterior, la función devolverá un par de valores enteros, uno mayor que sus parámetros.
Los valores múltiples devueltos por las funciones se asignan a las variables enumerándolos separados por comas, mientras que la cantidad de variables a las que se asigna el resultado de la llamada a la función debe coincidir exactamente con la cantidad de valores devueltos por la función:
primero , segundo := incDos ( 1 , 2 ) // primero = 2, segundo = 3 primero := incDos ( 1 , 2 ) // INCORRECTO - ninguna variable asignada al segundo resultadoA diferencia de Pascal y C, donde declarar una variable local sin usarla más tarde, o perder el valor de una variable local (cuando el valor asignado a la variable no se lee en ninguna parte) solo puede causar una advertencia del compilador, en Go esta situación se considera un error de idioma y conduce a la imposibilidad de compilar el programa. Esto significa, en particular, que el programador no puede ignorar el valor (o uno de los valores) devuelto por la función, simplemente asignándolo a alguna variable y negándose a usarlo más. Si es necesario ignorar uno de los valores devueltos por una llamada de función, se usa una pseudovariable predefinida llamada "_" (un guión bajo). Se puede especificar en cualquier lugar donde debería haber una variable que tome un valor. El valor correspondiente no se asignará a ninguna variable y simplemente se perderá. El significado de tal decisión arquitectónica es identificar en la etapa de compilación una posible pérdida de resultados de cálculo: el compilador detectará una omisión accidental del procesamiento de valor, y el uso de la pseudovariable "_" indicará que el programador ignoró deliberadamente los resultados. En el siguiente ejemplo, si solo se necesita uno de los dos valores devueltos por la función incTwo, se debe especificar "_" en lugar de la segunda variable:
first := incTwo ( 1 , 2 ) // INVALID first , _ := incTwo ( 1 , 2 ) // TRUE, segundo resultado no utilizadoLa variable "_" se puede especificar en la lista de asignación cualquier número de veces. Todos los resultados de funciones que coincidan con "_" serán ignorados.
La llamada diferida reemplaza varias funciones sintácticas a la vez, en particular, los controladores de excepciones y los bloques de finalización garantizados. Una llamada de función precedida por la palabra clave defer se parametriza en el punto del programa donde se coloca y se ejecuta inmediatamente antes de que el programa salga del ámbito donde se declaró, independientemente de cómo y por qué se produce esta salida. Si una sola función contiene varias declaraciones de aplazamiento, las llamadas correspondientes se ejecutan secuencialmente después de que finaliza la función, en orden inverso. A continuación se muestra un ejemplo del uso de diferir como un bloque de finalización garantizado [15] :
// Función que copia el archivo func CopyFile ( dstName , srcName string ) ( escrito int64 , err error ) { src , err := os . Open ( srcName ) // Abre el archivo de origen si err != nil { // Verifica el retorno // Si falla, regresa con un error } // Si llegaste aquí, el archivo de origen se abrió con éxito defer src . Cerrar () // Llamada retrasada: se llamará a src.Close() cuando se complete CopyFile horario de verano , error : = os . Create ( dstName ) // Abre el archivo de destino si err != nil { // Verifica y regresa en caso de error } defer dst . Cerrar () // Llamada retrasada: se llamará a dst.Close() cuando se complete CopyFile regresoio ._ _ Copiar ( dst , src ) // Copiar datos y devolverlos desde la función // Después de llamar a todas las operaciones: primero dst.Close(), luego src.Close() }A diferencia de la mayoría de los lenguajes con sintaxis tipo C, Go no tiene paréntesis para construcciones condicionales for, if, switch:
si i >= 0 && i < len ( arr ) { println ( arr [ i ]) } ... for i := 0 ; yo < 10 ; yo ++ { } }Go utiliza una construcción de bucle para organizar todo tipo de bucles for.
for i < 10 { // bucle con condición previa, similar a while en C } para yo := 0 ; yo < 10 ; i ++ { // bucle con un contador, similar a for en C } for { // bucle infinito // La salida del bucle debe manejarse manualmente, // normalmente se hace con return o break } for { // bucle con poscondición ... // cuerpo del bucle si i >= 10 { // interrupción de la condición de salida } } for i , v := range arr { // recorrer la colección (array, slice, display) arr // i - índice (o clave) del elemento actual // v - copia del valor del elemento de matriz actual } for i := range arr { // recorrer la colección, solo se usa el índice } for _ , v := range arr { // recorrer la colección, usar solo valores de elementos } for range arr { // Recorre la colección sin variables (la colección se usa // solo como un contador de iteraciones). } for v := range c { // recorrer el canal: // v leerá los valores del canal c, // hasta que el canal sea cerrado por una gorutina concurrente // }La sintaxis del operador de opción múltiple switchtiene una serie de características. En primer lugar, a diferencia de C, no se requiere el uso del operador break: después de que se haya procesado la rama seleccionada, finaliza la ejecución del operador. Si, por el contrario, desea que la siguiente sucursal continúe procesando después de la sucursal seleccionada, debe utilizar el operador fallthrough:
cambiar valor { caso 1 : fmt . Println ( "One" ) fallthrough // A continuación, se ejecutará la rama "case 0:" case 0 : fmt . println ( "Cero" ) }Aquí, cuando value==1se mostrarán dos líneas, "Uno" y "Cero".
La expresión de elección y, en consecuencia, las alternativas en la declaración de cambio pueden ser de cualquier tipo, es posible enumerar varias opciones en una rama:
cambiar caracteres [ código ]. categoría { case "Lu" , "Ll" , "Lt" , "Lm" , "Lo" : ... case "Nd" : ... default : ... }Se permite la ausencia de una expresión de elección, en cuyo caso se deben escribir condiciones lógicas en las alternativas. Se ejecuta la primera rama, cuya condición es verdadera:
switch { case '0' <= c && c <= '9' : return c - '0' case 'a' <= c && c <= 'f' : return c - 'a' + 10 case 'A' <= c && c <= 'F' : devuelve c - 'A' + 10 }Un detalle importante: si una de las ramas con la condición termina con el operador fallthrough, luego de esta rama se procesará la siguiente, independientemente de que se cumpla su condición . Si desea que la siguiente rama se procese solo si se cumple su condición, debe usar construcciones secuenciales if.
El lenguaje Go no es compatible con la sintaxis estructurada de manejo de excepciones típica de la mayoría de los lenguajes modernos , que implica lanzar excepciones con un comando especial (generalmente throwo raise) y manejarlas en un bloque try-catch. En su lugar, se recomienda usar el retorno de error como uno de los resultados de la función (lo cual es útil, ya que en Go una función puede devolver más de un valor):
Muchos críticos del lenguaje creen que esta ideología es peor que el manejo de excepciones, ya que numerosas comprobaciones saturan el código y no permiten que todo el manejo de errores se concentre en bloques catch. Los creadores del lenguaje no consideran esto un problema grave. Se describen varios patrones de manejo de errores en Go (consulte, por ejemplo, el artículo de Rob Pike en el blog oficial de Go , traducción al ruso ), que pueden reducir la cantidad de código de manejo de errores.
Cuando se producen errores fatales que hacen que la ejecución del programa sea imposible (por ejemplo, la división por cero o el acceso a los límites de la matriz), se produce un estado de pánico que , de forma predeterminada, hace que el programa se bloquee con un mensaje de error y un seguimiento de la pila de llamadas. Los pánicos se pueden capturar y manejar utilizando la construcción de ejecución diferida deferdescrita anteriormente. La llamada a la función especificada en deferse realiza antes de abandonar el ámbito actual, incluso en caso de pánico. Dentro de la función llamada en defer, puede llamar a una función estándar recover() : detiene el procesamiento del sistema de un pánico y devuelve su causa en forma de un objeto errorque puede procesarse como un error normal. Pero el programador también puede reanudar un panic capturado previamente llamando al estándar panic(err error).
// El programa realiza una división entera // de su primer parámetro por el segundo // e imprime el resultado. func main () { diferir func () { err := recuperar () if v , ok := err .( error ); ok { // Manejo de un pánico correspondiente a un error de interfaz fmt . Fprintf ( os . Stderr , "Error %v \"%s\"\n" , err , v . Error ()) } else if err != nil { panic ( err ) // Manejo de errores inesperados: volver a generar el pánico. } }() a , err := strconv . ParseInt ( os . Args [ 1 ], 10 , 64 ) if err != nil { panic ( err ) } b , err := strconv . ParseInt ( os . Args [ 2 ], 10 , 64 ) if err != nil { panic ( err ) } fmt . fprintf ( os . Stdout , "%d / %d = %d\n" , a , b , a / b ) }En el ejemplo anterior, pueden ocurrir errores cuando la función convierte los argumentos del programa a números enteros strconv.ParseInt(). También es posible entrar en pánico al acceder a la matriz os.Args con un número insuficiente de argumentos, o al dividir por cero si el segundo parámetro es cero. Ante cualquier situación de error se genera un panic, el cual se procesa en la llamada defer:
> dividir 10 5 10 / 5 = 2 > dividir 10 0 Error runtime.errorString "error de tiempo de ejecución: número entero dividido por cero" > divide 10.5 2 Error *strconv.NumError "strconv.ParseInt: analizando "10.5": sintaxis no válida" > dividir 10 Error runtime.errorString "error de tiempo de ejecución: índice fuera de rango"Un panic no se puede desencadenar en una gorutina de ejecución paralela (ver más abajo) pero se puede manejar en otra. Tampoco se recomienda "pasar" pánico a través de un límite de paquete.
El modelo de subprocesamiento de Go se heredó del lenguaje Active Oberon basado en el CSP de Tony Hoare usando ideas de los lenguajes Occam y Limbo [9] , pero también están presentes funciones como el cálculo pi y la canalización.
Go le permite crear un nuevo hilo de ejecución de programa utilizando la palabra clave go , que ejecuta una función anónima o con nombre en una gorutina recién creada (término de Go para corrutinas ). Todas las gorutinas dentro del mismo proceso usan un espacio de direcciones común, ejecutándose en subprocesos del sistema operativo , pero sin vinculación fuerte a este último, lo que permite que una gorutina en ejecución deje un subproceso con una gorutina bloqueada (esperando, por ejemplo, para enviar o recibir un mensaje desde un canal) y continúe. La biblioteca de tiempo de ejecución incluye un multiplexor para compartir la cantidad disponible de núcleos del sistema entre rutinas. Es posible limitar el número máximo de núcleos de procesadores físicos en los que se ejecutará el programa. La autosuficiencia de goroutines por parte de la biblioteca de tiempo de ejecución de Go facilita el uso de grandes cantidades de goroutines en los programas, superando con creces el límite de la cantidad de subprocesos admitidos por el sistema.
func servidor ( i int ) { for { print ( i ) time . Dormir ( 10 ) } } ir al servidor ( 1 ) ir al servidor ( 2 )Los cierres se pueden usar en una expresión go .
var g int go func ( i int ) { s := 0 for j := 0 ; j < yo ; j ++ { s += j } g = s }( 1000 )Para la comunicación entre rutinas, se utilizan canales (el tipo integrado chan ), a través de los cuales se puede pasar cualquier valor. Un canal es creado por la función integrada make(), a la que se le pasa el tipo y (opcionalmente) el volumen del canal. Por defecto, el volumen del canal es cero. Dichos canales no tienen búfer . Puede configurar cualquier volumen entero positivo del canal, luego se creará un canal almacenado en búfer .
Una canalización sin búfer sincroniza estrechamente los subprocesos de lectura y escritura que la utilizan. Cuando un subproceso de escritor escribe algo en una tubería, se detiene y espera hasta que se haya leído el valor. Cuando un subproceso de lectura intenta leer algo de una tubería en la que ya se ha escrito, lee el valor y ambos subprocesos pueden continuar ejecutándose. Si aún no se ha escrito ningún valor en el canal, el subproceso del lector se detiene y espera a que alguien escriba en el canal. Es decir, las canalizaciones sin búfer en Go se comportan de la misma manera que las canalizaciones en Occam o el mecanismo de encuentro en el lenguaje Ada .
Un canal almacenado en búfer tiene un búfer de valor cuyo tamaño es igual al tamaño del canal. Al escribir en una tubería de este tipo, el valor se coloca en el búfer de la tubería y el subproceso del escritor continúa sin pausa, a menos que el búfer de la tubería esté lleno en el momento de la escritura. Si el búfer está lleno, el subproceso de escritura se suspende hasta que se haya leído al menos un valor del canal. El subproceso del lector también lee un valor de una tubería almacenada en búfer sin detenerse si hay valores no leídos en el búfer de la tubería; si el búfer del canal está vacío, el subproceso se detiene y espera hasta que otro subproceso le escriba un valor.
Cuando termine, el canal se puede cerrar con la función incorporada close(). Un intento de escribir en un canal privado da como resultado un pánico, la lectura de un canal privado siempre ocurre sin pausa y lee el valor predeterminado. Si el canal está almacenado en el búfer y al momento de cerrarlo contiene N valores previamente escritos en el búfer, entonces las primeras N operaciones de lectura se realizarán como si el canal todavía estuviera abierto y leerá los valores del búfer, y solo después de eso, la lectura del canal devolverá los valores predeterminados.
La operación se utiliza para pasar un valor hacia y desde un canal <-. Al escribir en un canal, se usa como operador binario, al leer, como operador unario:
in := make ( chan string , 0 ) // Crea un canal sin búfer in out := make ( chan int , 10 ) // Crea un canal con búfer out ... in <- arg // Escribe un valor en el canal in ... r1 := <- out // leyendo desde el canal out ... r2 , ok := <- out // leyendo comprobando si el canal está cerrado if ok { // if ok == true - el canal está abierto ... } else { // si el canal está cerrado, haz otra cosa ... }La operación de lectura de un canal tiene dos opciones: sin chequeo y con chequeo para cerrar el canal. La primera opción (leer r1 en el ejemplo anterior) simplemente lee el siguiente valor en la variable; si el canal está cerrado, el valor predeterminado se leerá en r1. La segunda opción (leer r2) lee, además del valor, un valor booleano: el indicador de estado del canal ok, que será verdadero si los datos colocados allí por cualquier flujo se han leído del canal, y falso si el canal es cerrado y su búfer está vacío. Con esta operación, el hilo del lector puede determinar cuándo se cierra el canal de entrada.
La lectura de una canalización también se admite mediante la construcción de bucle for-range:
// La función inicia la lectura paralela desde el canal de entrada en números enteros y escribe // en el canal de salida solo aquellos números enteros que son positivos. // Devuelve el canal de salida. func positives ( in <- chan int64 ) <- chan int64 { out := make ( chan int64 ) go func () { // El ciclo continuará hasta que el canal in se cierre para next := range in { if next > 0 { salir <- siguiente } } cerrar ( salir ) }() volver salir }Además de CSP o en conjunto con el mecanismo de canalización, Go también permite utilizar el modelo habitual de interacción sincronizada de hilos a través de memoria compartida, utilizando herramientas típicas de sincronización de acceso como mutexes . Al mismo tiempo, sin embargo, la especificación del lenguaje advierte contra cualquier intento de interacción no sincronizada de hilos paralelos a través de la memoria compartida, ya que en ausencia de sincronización explícita, el compilador optimiza el código de acceso a los datos sin tener en cuenta la posibilidad de acceso simultáneo desde diferentes subprocesos, lo que puede dar lugar a errores inesperados. Por ejemplo, escribir valores en variables globales en un subproceso puede no ser visible o verse en el orden incorrecto desde un subproceso paralelo.
Por ejemplo, considere el siguiente programa. El código de la función está main()escrito bajo el supuesto de que la función iniciada en goroutine setup()creará una estructura de tipo T, la inicializará con la cadena "hola, mundo" y luego asignará una referencia a la estructura inicializada a la variable global g. B main()inicia un ciclo vacío, esperando gque aparezca un valor distinto de cero. Tan pronto como aparece, main()imprime una cadena de la estructura a la que apunta g, asumiendo que la estructura ya se ha inicializado.
estructura tipo T { cadena de mensaje } varg * T _ configuración de funciones () { t : = nuevo ( T ) t . mensaje = "hola, mundo" g = t } func main () { ir a configuración () para g == nil { // ¡¡¡NO FUNCIONA!!! } imprimir ( g . msj ) }En realidad, uno de dos errores es posible.
La única forma correcta de organizar la transferencia de datos a través de la memoria compartida es utilizar herramientas de sincronización de bibliotecas, que garantizan que todos los datos escritos por uno de los flujos sincronizados antes del punto de sincronización estén disponibles en otro flujo sincronizado después del punto de sincronización.
Una característica de los subprocesos múltiples en Go es que una gorutina no se identifica de ninguna manera y no es un objeto de lenguaje al que se pueda hacer referencia al llamar a funciones o que se pueda colocar en un contenedor. En consecuencia, no existen medios que le permitan influir directamente en la ejecución de una rutina desde fuera de ella, como suspenderla y luego iniciarla, cambiar la prioridad, esperar la finalización de una rutina en otra e interrumpir la ejecución por la fuerza. Cualquier acción en una gorutina (aparte de finalizar el programa principal, que automáticamente finaliza todas las gorutinas) solo se puede realizar a través de conductos u otros mecanismos de sincronización. El siguiente es un código de muestra que inicia varias rutinas y espera a que se completen utilizando el objeto de sincronización WaitGroup del paquete del sistema de sincronización. Este objeto contiene un contador, inicialmente cero, que puede aumentar y disminuir, y un método Wait(), que hace que el subproceso actual se detenga y espere hasta que el contador se restablezca a cero.
func principal () { var wg sync . WaitGroup // Crea un grupo de espera. El valor inicial del contador es 0 logger := log . New ( os . Stdout , "" , 0 ) // log.Logger es un tipo de salida seguro para subprocesos para _ , arg := range os . Args { // Recorre todos los argumentos de la línea de comandos wg . Add ( 1 ) // Incrementa el contador del grupo de espera en uno // Ejecuta una gorutina para procesar el parámetro arg go func ( cadena de palabras ) { // Decremento retrasado del contador del grupo de espera en uno. // Ocurre cuando termina la función. aplazar wg . Hecho () registrador . Println ( preparaPalabra ( palabra )) // Realiza el procesamiento e imprime el resultado }( arg ) } wg . Wait () // Espere hasta que el contador en el grupo de espera wg sea cero. }Aquí, antes de la creación de cada nueva gorutina, el contador del objeto wg se incrementa en uno, y después de completar la gorutina, se decrementa en uno. Como resultado, en el ciclo que inicia el procesamiento de argumentos, se agregarán al contador tantas unidades como rutinas haya lanzadas. Cuando finaliza el ciclo, llamar a wg.Wait() hará que el programa principal se detenga. A medida que se completa cada una de las gorrutinas, disminuye el contador wg en uno, por lo que la espera del programa principal terminará cuando se hayan completado tantas gorrutinas como las que se estaban ejecutando. Sin la última línea, el programa principal, habiendo ejecutado todas las rutinas, saldría inmediatamente, interrumpiendo la ejecución de aquellas que no tuvieron tiempo de ejecutarse.
A pesar de la presencia de subprocesos múltiples integrados en el lenguaje, no todos los objetos de lenguaje estándar son seguros para subprocesos. Por lo tanto, el tipo de mapa estándar (pantalla) no es seguro para subprocesos. Los creadores del lenguaje explicaron esta decisión con consideraciones de eficiencia, ya que garantizar la seguridad de todos esos objetos llevaría a una sobrecarga adicional, que está lejos de ser siempre necesaria (las mismas operaciones con asignaciones pueden ser parte de operaciones más grandes que ya están sincronizadas por el programador , y luego la sincronización adicional solo complicará y ralentizará el programa). A partir de la versión 1.9, el paquete de la biblioteca de sincronización, que contiene soporte de procesamiento paralelo, ha agregado el tipo sync.Map seguro para subprocesos, que se puede usar si es necesario. También puede prestar atención al tipo seguro para subprocesos que se usa en el ejemplo para mostrar los resultados log.Logger; se usa en lugar del paquete fmt estándar, cuyas funciones (Printf, Println, etc.) no son seguras para subprocesos y requerirían una sincronización adicional.
No hay una palabra clave especial para declarar una clase en Go, pero se pueden definir métodos para cualquier tipo con nombre, incluidas estructuras y tipos base como int , por lo que en el sentido de programación orientada a objetos, todos estos tipos son clases.
escriba newInt intLa sintaxis de definición del método se toma prestada del lenguaje Oberon-2 y se diferencia de la definición de función habitual en que, después de la palabra clave func, se declara entre paréntesis el llamado "receptor" ( receptor en inglés ) , es decir, el objeto para el cual el se llama el método y el tipo al que pertenece el método. Mientras que en los lenguajes de objetos tradicionales el receptor está implícito y tiene un nombre estándar (en C++ o Java es "this", en ObjectPascal es "self", etc.), en Go se especifica explícitamente y su nombre puede ser cualquier identificador de Go válido.
type myType struct { i int } // Aquí p es el receptor en métodos de tipo myType. func ( p * myType ) get () int { return p . i } func ( p * myType ) set ( i int ) { p . yo = yo }No hay herencia formal de clases (estructuras) en Go, pero hay un mecanismo de incrustación técnicamente cercano . En la descripción de la estructura, puede utilizar el llamado campo anónimo, un campo para el que no se indica un nombre, sino solo un tipo. Como resultado de tal descripción, todos los elementos de la estructura empotrada se convertirán en los elementos del mismo nombre de la estructura empotrada.
// Nuevo tipo de estructura type myType2 struct { myType // El campo anónimo proporciona incrustaciones de tipo myType. // Ahora myType2 contiene el campo i y los métodos get() y set(int). k int }A diferencia de la herencia clásica, la inserción no implica un comportamiento polimórfico (un objeto de una clase incrustada no puede actuar como un objeto de una clase incrustable sin una conversión de tipo explícita).
No es posible declarar explícitamente métodos para un tipo sin nombre (la sintaxis simplemente no le permite especificar el tipo del receptor en el método), pero esta limitación se puede eludir fácilmente insertando el tipo con nombre con los métodos necesarios.
El polimorfismo de clase se proporciona en Go mediante el mecanismo de las interfaces (similar a las clases completamente abstractas en C++ ). Una interfaz se describe utilizando la palabra clave interface; dentro (a diferencia de las declaraciones de tipo de clase) las descripciones declaran los métodos proporcionados por la interfaz.
escriba la interfaz myInterface { get () int set ( i int ) }En Go, no es necesario indicar explícitamente que un tipo implementa una interfaz en particular. En cambio, la regla es que cada tipo que proporciona métodos definidos en una interfaz puede usarse como una implementación de esa interfaz. El tipo declarado anteriormente myTypeimplementa la interfaz myInterface, aunque esto no se indica explícitamente en ninguna parte, porque contiene métodos get()y set()cuyas firmas coinciden con las descritas en myInterface.
Al igual que las clases, las interfaces pueden estar en línea:
escriba mySecondInterface interface { myInterface // igual que declarar explícitamente get() int; establecer (i int) cambiar ( i int ) int }Aquí, la interfaz mySecondInterface hereda la interfaz myInterface (es decir, declara que expone los métodos incluidos en myInterface) y, además, declara un método nativo change().
Si bien es posible, en principio, construir un programa Go en una jerarquía de interfaces, como se hace en otros lenguajes de objetos, e incluso simular la herencia, esto se considera una mala práctica. El lenguaje no dicta un enfoque jerárquico, sino composicional del sistema de clases e interfaces. Las clases de estructura con este enfoque generalmente pueden permanecer formalmente independientes, y las interfaces no se combinan en una sola jerarquía, sino que se crean para aplicaciones específicas, si es necesario, incorporando las existentes. La implementación implícita de interfaces en Go proporciona a estos mecanismos una extrema flexibilidad y un mínimo de dificultades técnicas en su uso.
Este enfoque de la herencia está en línea con algunas tendencias prácticas en la programación moderna. Entonces, en el famoso libro "pandilla de cuatro" ( Erich Gamma y otros) sobre patrones de diseño , en particular, está escrito:
La dependencia de implementación puede causar problemas al intentar reutilizar una subclase. Si incluso un aspecto de la implementación heredada no es adecuado para el nuevo dominio, entonces la clase principal debe reescribirse o reemplazarse con algo más adecuado. Esta dependencia limita la flexibilidad y la reutilización. El problema se puede superar heredando solo de clases abstractas, ya que generalmente tienen una implementación mínima o nula.
No existe el concepto de una función virtual en Go . El polimorfismo lo proporcionan las interfaces. Si se utiliza una variable de un tipo ordinario para llamar a un método, dicha llamada está vinculada estáticamente, es decir, siempre se llama al método definido para este tipo en particular. Si se llama al método para una variable del tipo "interfaz", entonces dicha llamada se vincula dinámicamente y, en el momento de la ejecución, la variante del método que se define para el tipo de objeto realmente asignado en el momento de llamar a este se selecciona la variable para el lanzamiento.
El proyecto GOOP proporciona soporte dinámico para la programación orientada a objetos para Go .
La capacidad de introspección en tiempo de ejecución, es decir, acceso y procesamiento de valores de cualquier tipo y ajuste dinámico a los tipos de datos que se procesan, se implementa en Go mediante el paquete del sistema reflect. Este paquete le permite:
El paquete también reflectcontiene muchas herramientas auxiliares para realizar operaciones según el estado dinámico del programa.
Las instalaciones de acceso a la memoria de bajo nivel se concentran en el paquete del sistema unsafe. Su peculiaridad es que, aunque parece un paquete Go normal, en realidad lo implementa el propio compilador. El paquete unsafeproporciona acceso a la representación interna de datos ya punteros de memoria "reales". Proporciona características:
El paquete también proporciona un tipo unsafe.Pointerque se puede convertir en cualquier puntero y se puede convertir en un puntero de cualquier tipo, así como un tipo estándar uintptr , un valor entero sin signo lo suficientemente grande como para almacenar una dirección completa en la plataforma actual. Al convertir el puntero a unsafe.Pointery luego a uintptr, puede obtener la dirección como un número entero, al que puede aplicar operaciones aritméticas. Al volver a convertir el valor unsafe.Pointeren un puntero a cualquier tipo en particular, puede acceder a casi cualquier parte del espacio de direcciones de esta manera.
Las transformaciones descritas pueden ser inseguras, por lo que se recomienda evitarlas en la medida de lo posible. Primero, existen problemas obvios asociados con el acceso erróneo al área de memoria equivocada. Un punto más sutil es que, a pesar del uso del paquete unsafe, los objetos de Go continúan siendo administrados por el administrador de memoria y el recolector de basura. Convertir un puntero en un número pone ese puntero fuera de control, y el programador no puede esperar que dicho puntero convertido siga siendo relevante indefinidamente. Por ejemplo, intentar almacenar un puntero a un nuevo tipo de objeto como Тeste:
pT := uintptr ( inseguro . Pointer ( new ( T ))) // ¡INCORRECTO!hará que se cree el objeto, el puntero a él se convertirá en un número (que se asignará a pT). Sin embargo, pTtiene un tipo entero y el recolector de elementos no utilizados no lo considera un puntero a un objeto creado, por lo que una vez que se completa la operación, el sistema de administración de memoria considerará que este objeto no se usa. Es decir, puede ser eliminado por el recolector de elementos no utilizados, después de lo cual el puntero convertido pTdejará de ser válido. Esto puede ocurrir en cualquier momento, tanto inmediatamente después de la finalización de la operación como después de muchas horas de funcionamiento del programa, por lo que el error se expresará en fallas aleatorias del programa, cuya causa será extremadamente difícil de identificar. Y cuando se usa un recolector de basura en movimiento [* 1] , el puntero convertido en un número puede volverse irrelevante incluso cuando el objeto aún no se ha eliminado de la memoria.
Dado que la especificación de Go no da indicaciones precisas de hasta qué punto un programador puede esperar mantener actualizado un puntero convertido en un número, hay una recomendación: mantener dichas conversiones al mínimo y organizarlas de modo que la conversión de el puntero original, sus modificaciones y la conversión inversa están dentro de una instrucción de idioma, y al llamar a cualquier función de biblioteca que devuelva una dirección en forma de uintptr, convierta inmediatamente su resultado a unsafe.Pointerpara preservar la garantía de que el puntero no se perderá.
El paquete unsaferara vez se usa directamente en la programación de aplicaciones, pero se usa activamente en los paquetes reflect, os, syscall, contexty netalgunos otros.
Hay varias herramientas externas que proporcionan interfaces de funciones externas (FFI) para programas Go. La utilidad cgo se puede usar para interactuar con código C externo (o tener una interfaz compatible con C) . Se llama automáticamente cuando el compilador procesa un módulo de Go correctamente escrito y garantiza la creación de un paquete contenedor de Go temporal que contiene declaraciones de todos los tipos y funciones necesarios. En las llamadas a funciones de C, a menudo tiene que recurrir a las instalaciones de paquetes , principalmente usando el . Una herramienta más potente es SWIG [16] , que proporciona funciones más avanzadas, como la integración con clases de C++ . unsafeunsafe.Pointer
La biblioteca estándar de Go admite la creación de aplicaciones de consola y aplicaciones de servidor basadas en web , pero no existen herramientas estándar para crear GUI en aplicaciones cliente. Este vacío se llena con envoltorios de terceros para marcos de interfaz de usuario populares como GTK+ y Qt , en Windows puede usar las herramientas gráficas de WinAPI accediendo a ellas a través del paquete syscall, pero todos estos métodos son bastante engorrosos. También hay varios desarrollos de marcos de interfaz de usuario en Go, pero ninguno de estos proyectos ha alcanzado el nivel de aplicabilidad industrial. En 2015, en la conferencia GopherCon en Denver , uno de los creadores del lenguaje, Robert Grismer, respondió preguntas y estuvo de acuerdo en que Go necesita un paquete de interfaz de usuario, pero señaló que dicho paquete debería ser universal, potente y multiplataforma, lo que hace que su desarrollo proceso largo y dificil. La cuestión de implementar una GUI de cliente en Go sigue abierta.
Debido a la juventud del lenguaje, su crítica se concentra principalmente en artículos de Internet, reseñas y foros.
Gran parte de la crítica al idioma se centra en la falta de ciertas características populares proporcionadas por otros idiomas. Entre ellos [17] [18] [19] [20] :
Como se mencionó anteriormente, la falta de una serie de funciones disponibles en otros lenguajes populares se debe a la elección consciente de los desarrolladores, quienes creen que dichas funciones dificultan la compilación eficiente o provocan que el programador cometa errores o cree ineficiente o "malo" en términos de mantenimiento del código, o tiene otros efectos secundarios no deseados.
Los críticos señalan que algunas características de Go se implementan en términos de la implementación más simple o más eficiente, pero no cumplen con el " principio de menor sorpresa ": su comportamiento difiere de lo que el programador esperaría según la intuición y la experiencia pasada. Estas características requieren una mayor atención por parte del programador, dificultan el aprendizaje y el cambio de otros idiomas.
A menudo se critica el mecanismo de punto y coma automático, debido a que algunas formas de escribir declaraciones, llamadas a funciones y listas se vuelven incorrectas. Comentando esta decisión, los autores del lenguaje señalan [9] que, junto con la presencia de un formateador de código en el conjunto de herramientas oficial, gofmtcondujo a la fijación de un estándar bastante rígido para la codificación en Go. Es casi imposible crear un estándar para escribir código que se adapte a todos; la introducción en el lenguaje de una característica que en sí misma establece tal estándar, unifica la apariencia de los programas y elimina los conflictos sin principios debido al formateo, lo que es un factor positivo para el desarrollo y mantenimiento grupal del software.
La popularidad de Go ha crecido en los últimos años: de 2014 a 2020, Go ha subido del puesto 65 al 11 en el ranking TIOBE , el valor de calificación para agosto de 2020 es de 1,43%. Según los resultados de una encuesta de dou.ua [22] , el lenguaje Go en 2018 se convirtió en el noveno en la lista de los más utilizados y el sexto en la lista de lenguajes a los que los desarrolladores dan preferencia personal.
Desde el primer lanzamiento público en 2012, el uso del lenguaje ha ido en constante crecimiento. La lista de empresas que utilizan el lenguaje en el desarrollo industrial publicada en el sitio web del proyecto Go incluye varias decenas de nombres. Se ha acumulado una gran variedad de bibliotecas para varios propósitos. El lanzamiento de la versión 2.0 estaba previsto para 2019, pero el trabajo se retrasó y aún está en curso para la segunda mitad de 2022. Se espera que aparezcan varias funciones nuevas , incluidas genéricas y sintaxis especial para simplificar el manejo de errores, cuya ausencia es una de las quejas más comunes de los críticos del lenguaje .
El servidor web RoadRunner (servidor de aplicaciones) se desarrolló en Golang , lo que permite que las aplicaciones web alcancen una velocidad de solicitud-respuesta de 10-20 ms en lugar de los tradicionales 200 ms. Este servicio web está planeado para incluirse en marcos populares como Yii .
Junto con C ++ , Golang se usa para desarrollar microservicios, lo que le permite "cargar" plataformas de múltiples procesadores con trabajo. Puede interactuar con un microservicio usando REST , y el lenguaje PHP es excelente para esto.
El Framework Espiral fue desarrollado con la ayuda de PHP y Golang. [23]
Solo existe una versión principal del propio lenguaje Go, la versión 1. Las versiones del entorno de desarrollo Go (compilador, herramientas y bibliotecas estándar) están numeradas con dos dígitos ("<versión del idioma>.<versión principal>") o tres dígitos ("<versión de idioma>.<versión principal>.<versión secundaria>") al sistema. El lanzamiento de una nueva versión de "dos dígitos" finaliza automáticamente el soporte para la versión anterior de "dos dígitos". Se lanzan versiones de "tres dígitos" para corregir errores informados y problemas de seguridad; las correcciones de seguridad en dichas versiones pueden afectar a las dos últimas versiones de "dos dígitos" [24] .
Los autores declararon [25] el deseo de preservar, en la medida de lo posible, la retrocompatibilidad dentro de la versión principal del lenguaje. Esto significa que antes del lanzamiento de Go 2, casi cualquier programa creado en el entorno de Go 1 se compilará correctamente en cualquier versión posterior de Go 1.xy se ejecutará sin errores. Las excepciones son posibles, pero son pocas. Sin embargo, la compatibilidad binaria entre versiones no está garantizada, por lo que un programa debe volver a compilarse por completo al pasar a una versión posterior de Go.
Desde marzo de 2012, cuando se presentó Go 1, se han lanzado las siguientes versiones principales:
El progreso del desarrollo Desde 2017, se están realizando preparativos para el lanzamiento de la próxima versión básica del lenguaje, que tiene el símbolo "Go 2.0" [26] . La colección de comentarios a la versión actual y propuestas de transformaciones, acumulada en el sitio wiki del proyecto [27] . Inicialmente, se asumió que el proceso de preparación tomaría "alrededor de dos años", y algunos de los nuevos elementos del lenguaje se incluirían en los próximos lanzamientos de la versión Go 1 (por supuesto, solo aquellos que no violen la compatibilidad con versiones anteriores). ). [26] A abril de 2021, la versión 2.0 aún no está lista, algunos de los cambios planificados se encuentran en etapa de diseño e implementación. De acuerdo con los planes descritos en el blog del proyecto [28] , el trabajo en la implementación de los cambios planificados continuará durante al menos 2021. Innovaciones sugeridas Entre las innovaciones fundamentales se encuentran valores constantes declarados explícitamente, un nuevo mecanismo de manejo de errores y herramientas de programación genéricas. Los proyectos de innovación están disponibles en línea. El 28 de agosto de 2018, se publicó en el blog oficial de desarrolladores un video presentado anteriormente en la conferencia Gophercon 2018 , que muestra versiones preliminares del nuevo diseño de manejo de errores y mecanismo de funciones genéricas. También hay muchos cambios menos notables pero muy significativos planeados, [29] como expandir las reglas para la admisibilidad de caracteres para identificadores en alfabetos no latinos, permitir operaciones de desplazamiento para enteros con signo, usar el guión bajo como separador de grupos de miles en números, literales binarios. La mayoría de ellos ya están implementados y disponibles en las últimas versiones de Go 1. Procesamiento de errores Se consideraron varias opciones para modificar el mecanismo de manejo de errores, en particular, un diseño con un manejador de errores separado (" Manejo de errores - Diseño preliminar "). La última variante para julio de 2019 se describe en el artículo " Propuesta: una función de comprobación de errores de Go integrada, inténtelo ". Esta opción es la más minimalista e implica agregar solo una función integrada try()que procesa el resultado de una llamada de función. Su uso se ilustra con el pseudocódigo a continuación. func f ( ... )( r1 type_1 , ... , rn type_n , err error ) { // Función probada // Devuelve n+1 resultados: r1... rn, err de tipo error. } func g ( … )( … , err error ) { // Llamar a la función f() con comprobación de errores: … x1 , x2 , … xn = try ( f ( … )) // Usar la función incorporada try: // if f( ) devuelto no nulo en el último resultado, entonces g() terminará automáticamente, // devolviendo el mismo valor en el último resultado de ITS. … } func t ( … )( … , err error ) { // Similar a g() sin usar la nueva sintaxis: t1 , t2 , … tn , te := f ( … ) // Llamar a f() con resultados almacenados en forma temporal variables if te != nil { // Verifica la igualdad del código de retorno nil err = te // Si el código de retorno no es nil, entonces se escribe en el último resultado de t(), return // después de lo cual t() termina inmediatamente. } // Si no hubo error, x1 , x2 , … xn = t1 , t2 , … tn // … las variables x1…xn obtienen sus valores // y continúa la ejecución de t(). … } Es decir, try()simplemente proporciona una verificación de error en la llamada a la función que se está verificando y un retorno inmediato de la función actual con el mismo error. Puede usar el mecanismo para manejar un error antes de regresar de la función actual defer. El uso try()requiere que tanto la función que se comprueba como la función en la que se llama deben tener el último valor de retorno de tipo error. Por lo tanto, por ejemplo, no puede main()usar try(); en el nivel superior, todos los errores deben manejarse explícitamente. Se suponía que este mecanismo de manejo de errores se incluiría en Go 1.14 , pero no se hizo. No se especifican las fechas de implementación. Código genérico A fines de 2018, se presentó un borrador de implementación de tipos y funciones genéricos en Go [30] . El 9 de septiembre de 2020, se publicó un diseño revisado [31] en el que las funciones, los tipos y los parámetros de funciones se pueden parametrizar mediante tipos de parámetros que, a su vez, están controlados por restricciones . // Stringer es una interfaz de restricción que requiere que el tipo implemente // un método String que devuelve un valor de cadena. escriba Stringer interfaz { Cadena () cadena } // La función recibe como entrada una matriz de valores de cualquier tipo que implemente el método String, y devuelve // la matriz de cadenas correspondiente obtenida al llamar al método String para cada elemento de la matriz de entrada. func Stringify [ T Stringer ] ( s [] T ) [] string { // el parámetro de tipo T, sujeto a la restricción Stringer, // es el tipo de valor del parámetro de matriz s. ret = make ([] string , len ( s )) for i , v := range s { ret [ i ] = v . String () } return ret } ... v := make ([] MyType ) ... // Para llamar a una función genérica, debe especificar un tipo específico s := Stringify [ String ]( v ) Aquí, la función Stringifycontiene un parámetro de tipo T, que se utiliza en la descripción de un parámetro regular s. Para llamar a una función de este tipo, como se muestra en el ejemplo, debe especificar en la llamada el tipo específico para el que se llama. Stringeren esta descripción, es una restricción que requiere que MyType implemente un Stringmétodo sin parámetros que devuelva un valor de cadena. Esto permite que el compilador procese correctamente la expresión " " v.String(). La implementación del código genérico se anuncia en la versión 1.18, prevista para agosto de 2021. [28]
Actualmente hay dos compiladores principales de Go:
También hay proyectos:
El entorno de desarrollo de Go contiene varias herramientas de línea de comandos: la utilidad go, que proporciona compilación, prueba y gestión de paquetes, y las utilidades auxiliares godoc y gofmt, diseñadas para documentar programas y dar formato al código fuente de acuerdo con reglas estándar, respectivamente. Para mostrar una lista completa de herramientas, debe llamar a la utilidad go sin especificar argumentos. El depurador gdb se puede usar para depurar programas. Los desarrolladores independientes proporcionan una gran cantidad de herramientas y bibliotecas diseñadas para respaldar el proceso de desarrollo, principalmente para facilitar el análisis, la prueba y la depuración del código.
Actualmente, hay dos IDE disponibles que originalmente se centraron en el lenguaje Go: este es el propietario GoLand [1] (desarrollado por JetBrains en la plataforma IntelliJ) y el LiteIDE gratuito [2] (anteriormente, el proyecto se llamaba GoLangIDE). LiteIDE es un pequeño shell escrito en C++ usando Qt . Le permite compilar, depurar, formatear código, ejecutar herramientas. El editor admite el resaltado de sintaxis y el autocompletado.
Go también es compatible con complementos en los IDE universales Eclipse, NetBeans, IntelliJ, Komodo, CodeBox IDE, Visual Studio, Zeus y otros. El resaltado automático, la finalización automática del código Go y la ejecución de las utilidades de compilación y procesamiento de código se implementan como complementos para más de dos docenas de editores de texto comunes para varias plataformas, incluidas Emacs, Vim, Notepad ++, jEdit.
A continuación se muestra un ejemplo de "¡Hola, mundo!" en el idioma Go.
paquete principal importar "fmt" función principal () { fmt . println ( "¡Hola, mundo!" ) }Un ejemplo de implementación del comando echo de Unix :
paquete principal import ( "os" "flag" // analizador de línea de comando ) var omitirNuevaLínea = bandera . Bool ( "n" , falso , "no imprimir nueva línea" ) const ( Espacio = " " NuevaLínea = "\n" ) función principal () { bandera . Parse () // Escanea la lista de argumentos y establece banderas var s string for i := 0 ; yo < bandera . Narg (); i ++ { si i > 0 { s += Espacio } s += bandera . Arg ( i ) } si ! * omitirNuevaLínea { s += NuevaLínea } os . Salida estándar . Escribir Cadena ( s ) } ![]() | |
---|---|
sitios temáticos | |
En catálogos bibliográficos |
Lenguajes de programación | |
---|---|
|