¿Cuál es el significado de la interfaz {}?


133

Soy nuevo en interfaces e intento hacer una solicitud SOAP por github

No entiendo el significado de

Msg interface{}

en este código:

type Envelope struct {
    Body `xml:"soap:"`
}

type Body struct {
    Msg interface{}
}

He observado la misma sintaxis en

fmt.Println

pero no entiendo lo que se está logrando con

interface{}

20
interface{}es más o menos el equivalente de void *en C. Puede apuntar a cualquier cosa y necesita una aserción de conversión / tipo para usarlo.
Nick Craig-Wood

¿Cuál es el significado de la interfaz {}? Ver stackoverflow.com/a/62337836/12817546 .
Tom J

Respuestas:


189

Puede consultar el artículo " Cómo usar interfaces en Go " (basado en la " Descripción de interfaces de Russ Cox "):

¿Qué es una interfaz?

Una interfaz es dos cosas:

  • es un conjunto de métodos
  • pero también es un tipo

El interface{}tipo, la interfaz vacía es la interfaz que no tiene métodos.

Como no hay una palabra clave de implementos, todos los tipos implementan al menos cero métodos, y la satisfacción de una interfaz se realiza automáticamente, todos los tipos satisfacen la interfaz vacía .
Eso significa que si escribe una función que toma un interface{}valor como parámetro, puede proporcionar esa función con cualquier valor .

(Eso es lo que Msgrepresenta en su pregunta: cualquier valor)

func DoSomething(v interface{}) {
   // ...
}

Aquí es donde se vuelve confuso:

dentro de la DoSomethingfunción, ¿cuál es vel tipo?

A los principiantes se les hace creer que " ves de cualquier tipo", pero eso está mal.
vno es de ningún tipo; Es de interface{}tipo .

Al pasar un valor a la DoSomethingfunción, el tiempo de ejecución de Go realizará una conversión de tipo (si es necesario) y convertirá el valor en un interface{}valor .
Todos los valores tienen exactamente un tipo en tiempo de ejecución, y vel único tipo estático es interface{}.

Un valor de interfaz se construye con dos palabras de datos :

  • una palabra se usa para señalar una tabla de métodos para el tipo subyacente del valor,
  • y la otra palabra se usa para señalar los datos reales retenidos por ese valor.

Anexo: Esto es donde el artículo de Russ es bastante completo con respecto a una estructura de interfaz:

type Stringer interface {
    String() string
}

Los valores de la interfaz se representan como un par de dos palabras que proporciona un puntero a la información sobre el tipo almacenado en la interfaz y un puntero a los datos asociados.
Asignar b a un valor de interfaz de tipo Stringer establece ambas palabras del valor de interfaz.

http://research.swtch.com/gointer2.png

La primera palabra en el valor de la interfaz apunta a lo que yo llamo una tabla de interfaz o itable (pronunciado i-table; en las fuentes de tiempo de ejecución, el nombre de implementación de C es Itab).
El itable comienza con algunos metadatos sobre los tipos involucrados y luego se convierte en una lista de punteros de función.
Tenga en cuenta que el itable corresponde al tipo de interfaz, no al tipo dinámico .
En términos de nuestro ejemplo, la opción para Stringermantener el tipo Binary enumera los métodos utilizados para satisfacer a Stringer, que es simplemente String: Los otros métodos de Binary ( Get) no aparecen en el itable.

La segunda palabra en el valor de la interfaz apunta a los datos reales , en este caso una copia de b.
La asignación var s Stringer = bhace una copia de, en blugar de señalar, bpor la misma razón que var c uint64 = bhace una copia: si bmás tarde cambia, sy cse supone que tiene el valor original, no el nuevo.
Los valores almacenados en las interfaces pueden ser arbitrariamente grandes, pero solo una palabra está dedicada a mantener el valor en la estructura de la interfaz, por lo que la asignación asigna una porción de memoria en el montón y registra el puntero en el espacio de una palabra.


44
¿Qué quieres decir con "dos palabras de datos"? Específicamente, ¿qué significa la "palabra"?
Mingyu

3
@Mingyu He completado la respuesta para ilustrar esas dos palabras (puntos de 32 bits).
VonC

2
@Mingyu: VonC se refiere a una palabra en el sentido de la arquitectura de la computadora: una colección de bits que definen un dato de tamaño fijo. El tamaño de palabra se rige por la arquitectura del procesador que está utilizando.
Dan Esparza

1
gracias @VonC por tu respuesta ... lo cierto es que estoy cansado de tener un puesto de avanzada cuando pregunto cosas ... la mayoría de las veces la gente me dice que debería leer los documentos ... recordaré tu sugerencia si me siento con voluntad de escribir correctamente una publicación para ello ... pero realmente no puedo pensar en otra forma de preguntar. Así que gracias de todos modos y disculpe mi baja voluntad. Le invitamos a echar un vistazo a esto: stackoverflow.com/questions/45577301/… para aclarar por qué no me gusta preguntar.
Victor

1
@vic no hay problema, y ​​lo siento por su mala experiencia anterior como autor de la pregunta. Es solo que los comentarios no encajan bien con las preguntas y respuestas.
VonC

34

interface{}significa que puede poner valor de cualquier tipo, incluido su propio tipo personalizado. Todos los tipos en Go satisfacen una interfaz vacía ( interface{}es una interfaz vacía).
En su ejemplo, el campo Msg puede tener un valor de cualquier tipo.

Ejemplo:

package main

import (
    "fmt"
)

type Body struct {
    Msg interface{}
}

func main() {
    b := Body{}
    b.Msg = "5"
    fmt.Printf("%#v %T \n", b.Msg, b.Msg) // Output: "5" string
    b.Msg = 5

    fmt.Printf("%#v %T", b.Msg, b.Msg) //Output:  5 int
}

Ir al patio de recreo


12

Se llama interfaz vacía y está implementado por todos los tipos, lo que significa que puede poner cualquier cosa en el Msgcampo.

Ejemplo:

body := Body{3}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:3}

body = Body{"anything"}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:"anything"}

body = Body{body}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:main.Body{Msg:"anything"}}

Esta es la extensión lógica del hecho de que un tipo implementa una interfaz tan pronto como tiene todos los métodos de la interfaz.


significa que podría ser una estructura definida por el usuario int?
usuario

11

Ya hay buenas respuestas aquí. Permítanme agregar el mío también para otros que quieran entenderlo intuitivamente:


Interfaz

Aquí hay una interfaz con un método:

type Runner interface {
    Run()
}

Entonces, cualquier tipo que tenga un Run()método satisface la interfaz Runner:

type Program struct {
    /* fields */
}

func (p Program) Run() {
    /* running */
}

func (p Program) Stop() {
    /* stopping */
}
  • Aunque el tipo de programa también tiene un método Stop, todavía satisface la interfaz Runner porque todo lo que se necesita es tener todos los métodos de una interfaz para satisfacerla.

  • Por lo tanto, tiene un método Run y ​​satisface la interfaz Runner.


Interfaz vacía

Aquí hay una interfaz vacía con nombre sin ningún método:

type Empty interface {
    /* it has no methods */
}

Entonces, cualquier tipo satisface esta interfaz. Porque no se necesita ningún método para satisfacer esta interfaz. Por ejemplo:

// Because, Empty interface has no methods, following types satisfy the Empty interface
var a Empty

a = 5
a = 6.5
a = "hello"

Pero, ¿el tipo de programa anterior lo satisface? Si:

a = Program{} // ok

interfaz {} es igual a la interfaz vacía anterior.

var b interface{}

// true: a == b

b = a
b = 9
b = "bye"

Como puede ver, no tiene nada de misterioso, pero es muy fácil abusar de él. Manténgase alejado de él tanto como pueda.


https://play.golang.org/p/A-vwTddWJ7G


El type Runner interfaceno se usa en el ejemplo Go playground.
Tom J

9

De las especificaciones de Golang :

Un tipo de interfaz especifica un conjunto de métodos llamado su interfaz. Una variable de tipo de interfaz puede almacenar un valor de cualquier tipo con un conjunto de métodos que sea un superconjunto de la interfaz. Se dice que este tipo implementa la interfaz. El valor de una variable no inicializada de tipo de interfaz es nulo.

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

interfaz{}

Los conceptos para graps son:

  1. Todo tiene un tipo . Se puede definir un nuevo tipo, vamos a llamarlo por ejemplo de T. Sea ahora nuestro tipo Ttiene 3 métodos: A, B, C.
  2. El conjunto de métodos especificados para un tipo se denomina " tipo de interfaz ". Llamémoslo en nuestro ejemplo: T_interface. Es igual aT_interface = (A, B, C)
  3. Puede crear un "tipo de interfaz" definiendo la firma de los métodos.MyInterface = (A, )
  4. Cuando especifica una variable de tipo , "tipo de interfaz", puede asignarle solo tipos que tengan una interfaz que sea un superconjunto de su interfaz. Eso significa que todos los métodos contenidos en MyInterfacedeben estar contenidos dentroT_interface

Puede deducir que todos los "tipos de interfaz" de todos los tipos son un superconjunto de la interfaz vacía.


1

Un ejemplo que extiende la excelente respuesta de @VonC y el comentario de @ NickCraig-Wood. interface{}puede apuntar a cualquier cosa y necesita una aserción de conversión / tipo para usarla.

package main

import (
    . "fmt"
    "strconv"
)

var c = cat("Fish")
var d = dog("Bone")

func main() {
    var i interface{} = c
    switch i.(type) {
    case cat:
        c.Eat() // Fish
    }

    i = d
    switch i.(type) {
    case dog:
        d.Eat() // Bone
    }

    i = "4.3"
    Printf("%T %v\n", i, i) // string 4.3
    s, _ := i.(string)      // type assertion
    f, _ := strconv.ParseFloat(s, 64)
    n := int(f)             // type conversion
    Printf("%T %v\n", n, n) // int 4
}

type cat string
type dog string
func (c cat) Eat() { Println(c) }
func (d dog) Eat() { Println(d) }

ies una variable de una interfaz vacía con un valor cat("Fish"). Es legal crear un valor de método a partir de un valor de tipo de interfaz. Ver https://golang.org/ref/spec#Interface_types .

Un interruptor de tipo confirma ique el tipo de interfaz es cat("Fish"). Ver https://golang.org/doc/effective_go.html#type_switch . iluego se reasigna a dog("Bone"). Un interruptor de tipo confirma que iel tipo de interfaz ha cambiado a dog("Bone").

También puede pedirle al compilador que verifique que el tipo T implementa la interfaz Ial intentar una asignación: var _ I = T{}. Ver https://golang.org/doc/faq#guarantee_satisfies_interface y https://stackoverflow.com/a/60663003/12817546 .

Todos los tipos implementan la interfaz vacía interface{}. Ver https://talks.golang.org/2012/goforc.slide#44 y https://golang.org/ref/spec#Interface_types . En este ejemplo, ise reasigna, esta vez a una cadena "4.3". iluego se asigna a una nueva variable de cadena scon i.(string)antess se convierte a un tipo float64 fusando strconv. Finalmente fse convierte a nun tipo int igual a 4. Vea ¿Cuál es la diferencia entre conversión de tipo y aserción de tipo?

Los mapas y sectores incorporados de Go, más la capacidad de usar la interfaz vacía para construir contenedores (con unboxing explícito) significan que en muchos casos es posible escribir código que haga lo que los genéricos permitirían, si no es así. Ver https://golang.org/doc/faq#generics .


Desacoplar código con una interfaz. Ver stackoverflow.com/a/62297796/12817546 . Llame a un método "dinámicamente". Ver stackoverflow.com/a/62336440/12817546 . Acceda a un paquete Go. Ver stackoverflow.com/a/62278078/12817546 . Asignar cualquier valor a una variable. Ver stackoverflow.com/a/62337836/12817546 .
Tom J
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.