Feda /dev/ - No Javascript allowed

Normas
spoiler
Personas non gratas
Memes feda dev




MTX_Anubis

#52920 los midudev indios

1 respuesta
PiradoIV

#52921 hindidev

5 1 respuesta
Kaledros

#52922 Tenías "hindudev" en bandeja, tío XDD

8
PiradoIV

No quiero que me cancelen, un saludo xD

2
Dr_Manhattan

nosotros tenemos a @eondev :clint:

1
Kaledros

Gentes que sabéis de Go

Estoy viendo mucho esto en el proyecto nuevo y tengo mis dudas. Tengo este struct:

type Thing struct {
    Name  string
    Amount   int
}

Y para crear una instancia (sé que no es una instancia pero no sé cómo llamarlo) tengo esto:

func NewThing(name string, amount int) *Thing {
    return &Thing{name, amount}
}

¿Esto se consideran buenas prácticas? ¿Y por qué declaro que NewThing() devuelve un puntero si está devolviendo la dirección de memoria?

2 respuestas
eondev

#52926 y que quieres si est';as devolviendo un puntero necesitarás la dirección de memoria no? xD

1 respuesta
Konishi

#52926 por ser tú va con GPT4 😘
Sí, el patrón que estás describiendo es considerado una buena práctica en Go, especialmente cuando se trata de la creación de instancias de estructuras. Este patrón se conoce como "constructor" por analogía con otros lenguajes de programación orientados a objetos, aunque Go no es un lenguaje orientado a objetos en el sentido tradicional. La función NewThing actúa como un constructor porque encapsula la inicialización de la estructura Thing.

Hay varias razones por las que este patrón es considerado una buena práctica:

  1. Encapsulamiento: Permite encapsular la lógica de inicialización de una estructura. Esto es útil si la creación de la estructura implica pasos adicionales de configuración o validación más allá de simplemente asignar valores a sus campos.

  2. Consistencia: Ayuda a mantener la consistencia en la forma en que se crean e inicializan las instancias. Esto es particularmente útil en proyectos grandes donde múltiples desarrolladores trabajan en el mismo código.

  3. Flexibilidad para cambios futuros: Si en el futuro necesitas cambiar la forma en que se inicializan las instancias de Thing, puedes hacerlo en un solo lugar (dentro de NewThing) en lugar de buscar y modificar cada instancia donde creaste un Thing directamente.

En cuanto a tu pregunta sobre por qué NewThing() devuelve un puntero (*Thing) en lugar de una instancia de Thing directamente, hay varias razones para esto también:

  1. Eficiencia: Devolver un puntero evita copiar toda la estructura cada vez que se crea una nueva instancia. Esto puede ser especialmente importante para estructuras grandes.

  2. Mutabilidad: Al devolver un puntero, cualquier modificación hecha a la instancia devuelta se refleja en todas las referencias a esa instancia. Si devolvieras una copia de la estructura (es decir, un valor en lugar de un puntero), cada modificación solo afectaría a esa copia local, lo que podría no ser el comportamiento deseado.

  3. Consistencia con el manejo de estructuras en Go: Es común en Go usar punteros a estructuras para permitir la mutabilidad y evitar copias innecesarias.

Finalmente, cuando dices que NewThing() "está devolviendo la dirección de memoria", es una forma de hablar sobre lo que ocurre bajo el capó. En Go, cuando usas el operador & delante de una estructura, estás obteniendo el puntero a la memoria donde se almacena esa estructura. Así que sí, técnicamente NewThing() devuelve un puntero a la dirección de memoria donde se ha creado y almacenado la nueva instancia de Thing.

PD: si alguien puede podría estar bien comparar respuestas, no me he molestado en hacer ningún prompt.

2 respuestas
Kaledros
#52927eondev:

est';as

Esta es nueva XDDDD

Y mi pregunta es por qué no devuelvo el puntero en lugar de la dirección.

2 respuestas
pineda

#52929 no entiendo muy bien la pregunta, estas devolviendo un puntero

6 1 respuesta
r2d2rigo

#52930 no entiendo muy bien la pregunta, estas devolviendo un putero.

1
LLoid

Como se entere alguno de por aquí de que no sabéis lo que es un puntero se lía

5
Dr_Manhattan

prepárate spiderman, lo de trabajar con alemanes va a quedar como anecdótico después de esto

1 respuesta
Kaledros

#52933 A mí mientras me lo explique bien explicado...

1 respuesta
Dr_Manhattan

#52934 xddd suerte con eso, espero que al menos uses vim

1 1 respuesta
Kaledros

#52935 De toda la vida en casa:

2
MTX_Anubis

#52929 si pusieras

return *Thing{name, amount}

Lo que estarías devolviendo entonces es el valor porque le estarías diciendo que haga la indirección y acceda al contenido. con &Thing le dices que devuelva la dirección de memoria.

Te digo esto habiendo tocado Go vagamente hace más de 10 años, que quizá el compilador hace otra cosa distinta xD

1 respuesta
Kaledros
#52937MTX_Anubis:

con &Thing le dices que devuelva la dirección de memoria

Esa parte la entiendo. Pero entonces estoy devolviendo una dirección, no un objeto.

#52937MTX_Anubis:

Lo que estarías devolviendo entonces es el valor porque le estarías diciendo que haga la indirección y acceda al contenido

¿Y no es eso lo que quiero hacer? Pregunto sin ironías. Si devuelvo

3 respuestas
SupermaN_CK
pineda

#52938

type Thing struct {
	Name   string
	Amount int
}

func NewThing(name string, amount int) *Thing {
	return &Thing{name, amount}
}

func modifier(t *Thing) {
	t.Amount = 20
}

func modifier2(t Thing) {
	t.Amount = 30
}

func main() {
	myVar := NewThing("thing1", 10)
	fmt.Printf("myVar (ptr) %v %T\n", myVar, myVar)
	fmt.Printf("myVar (struct) %v %T\n", *myVar, *myVar)
	fmt.Printf("myVar (ptr address) %v %T\n", &myVar, &myVar)

modifier(myVar)
fmt.Println(myVar.Amount)

modifier2(*myVar)
fmt.Println(myVar.Amount)
}

output

myVar (ptr) &{thing1 10} *main.Thing
myVar (struct) {thing1 10} main.Thing
myVar (ptr address) 0xc000072540 **main.Thing
20
20

lo que estas devolviendo en "NewThing" es un puntero al struct Thing

en este caso, la funcion modifier modifica el struct original
y la funcion modifier2 modifica el struct que le llega por parametro, que al no ser un puntero, es una copia del struct original

1 1 respuesta
MTX_Anubis

#52938 No, has declarado que quieres devolver un puntero (que es una dirección de memoria sobre el que se hará las indirecciones para acceder al valor del struct)

El &X es simplemente para poder acceder a la dirección de memoria de la variable

1 respuesta
desu

Yo no creo que eso sea una buena practica, al contrario, creo que esta mal.

Me refiero al "constructor".

Creo que es algo que se ha portado de los Javeros y demás fperos que venían de lenguajes orientados a objetos.

Pa que quieres:

name := "foo"
bar := 10
t := NewThing(name, amount)

Si puedes hacer:

name := "foo"
bar := 10
t0 := Thing{name, amount}
t1 := &Thing{name, amount}

no?? un poquito tonto

1 respuesta
desu

#52928 ese mensaje de chatgpt tiene bastantes cosas mal

el encapsulamiento por ejemplo, esta mal.. lo he explicado mil veces que encapsular es algo de los años 80, y que para el 85 ya teníamos "modulos", que de mejor o peor manera, Golang obviamente soporta. por tanto no es necesario y dejo de tener sentido hace casi 40 años.

no es mas eficiente devolver el & que el struct directamente otro ejemplo super obvio.... como va a ser mas eficiente un struct en la stack vs tener lo otro?

lo de mutabilidad es una tontería, cuantas copias vas a querer de ese struct??? para modificarlo desde N sitios? HAHAHA es que eso es una mala practica y si quieres tener N copias modificables pues ya lo harás tu a mano no?

lo de que si se tiene que cambiar el código habra que tocar mas sitios pues depende... ni eso.

Kaledros
#52942desu:

Creo que es algo que se ha portado de los Javeros y demás fperos que venían de lenguajes orientados a objetos.

Es que me sonaba justo a eso, lo veía un smell de querer picar OOP en un lenguaje no OOP, pero como aquí todo el mundo lo usa tenía mis dudas.

#52941 Lo entiendo, pero es la hostia de antiintuitivo declarar un tipo de return y devolver otra cosa.

2 respuestas
desu

#52944 yo también lo hago, esta mal y es lo que hay pero no es el fin del mundo... paso de explicarle a un fpero cada vez que quiera crear un struct que quiero usar el stack porque es algo que vivira poco tiempo o lo que sea...

el 99% de los programadores no sabe hacer la O

#52944Kaledros:

Lo entiendo, pero es la hostia de antiintuitivo declarar un tipo de return y devolver otra cosa.

simplemente te estas confundiendo con la sintaxis con esto

no es lo mismo

func NewThing(name string, amount int) *Thing {
	return &Thing{name, amount}
}

que

func NewThing(name string, amount int) *Thing {
	return *Thing{name, amount}
}

lo segundo "deferencia"

esto no existe (diria)

func NewThing(name string, amount int) &Thing {
	return &Thing{name, amount}
}

https://go.dev/play/

1
desu
#52938Kaledros:

Esa parte la entiendo. Pero entonces estoy devolviendo una dirección, no un objeto.

una dirección y un objeto es lo mismo, según tu definición y como supongo que tu lo entiendes, lo que pasa es que cuando accedes a la dirección, los lenguajes tienen syntax sugar para ahorrarte la derrefencia.

si tu en tu lenguaje (voy a hacer pseudo go, pero te vale para Java, PHP, lo que quieras) tienes:

struct Thing {
 name: string
 value: int
}

fun NewThing() *Thing {
   ...
}

y quieres modificar un valor haces esto:

 t := NewThing()
 t.name = "new name"

ese . y esa sintaxis es una convención generalizada. realmente haces:

 t := NewThing()
 (*t).name = "new name"

de la misma manera cuando tienes esto:

func (t Thing) foo() { }
func (t Thing) bar() { }

cuando lo llamas asi:

t.foo()
t.bar()

realmente estas haciendo:

(*t).foo()
(*t).bar()

como es algo tan común, el compilador lo hace por ti.

3 1 respuesta
Kaledros
#52946desu:

cuando accedes a la dirección, los lenguajes tienen syntax sugar para ahorrarte la derrefencia.

ESA era la pieza que me faltaba. Muchas gracias, tío.

desu
#52928Konishi:

En Go, cuando usas el operador & delante de una estructura, estás obteniendo el puntero a la memoria donde se almacena esa estructura.

Existen varios análisis que hace el compilador, entre ellos el mas famoso escape analisis. Cuando haces un & nivel de programador debes pensar: QUIER ESTO EN LA HEAP. Y tu supongo siempre que eso esta en la heap.

Seria un equivalente a hacer un Box en Rust, o a usar smart pointers en otros lenguajes como cpp.

Ahora, el escape análisis lo que hace por ejemplo, es seguir como vas a utilizar ese &, si detecta que es algo que vive poco tiempo, Go es suficiente inteligente como para meterlo en el stack en lugar de la heap.

Si tu sabes que quieres eso en el stack, lo mejor es no usar &.

En programas complejos, si usais estos patrones, un programador NO SABE donde esta poniendo las cosas. El problema es el dia que necesitas saberlo...

MTX_Anubis

#52944 El caso es que *Thing es un tipo (dirección que apunta a un struct Thing) y &Thing no existe como tipo, existe &<X> que yo lo entiendo como un operador que devuelve la dirección de memoria de <X> en vez de <X>

Es decir, lo que devuelve &thing es *Thing

Pero vamos que estoy contigo, le hubiera cambiado la sintaxis viendo que para hacer la indirección hay que hacer el *thing luego.

1 respuesta
desu
#52949MTX_Anubis:

Pero vamos que estoy contigo, le hubiera cambiado la sintaxis.

la sintaxis es standard en todos los lenguajes, tiene sus motivos el ser asi, por ejemplo sirve para que el compilador sepa el orden y que hacer cuando tienes muchos & intercalados y seguidos, y esto se da mucho cuando escribes cosas a bajo nivel.


#52940pineda:

en este caso, la funcion modifier modifica el struct original
y la funcion modifier2 modifica el struct que le llega por parametro, que al no ser un puntero, es una copia del struct original

deberiais escribir esto mejor:

// You can edit this code!
// Click here and start typing.
package main

import "fmt"

type Thing struct {
	Name   string
	Amount int
}

func NewThing(name string, amount int) *Thing {
	return &Thing{name, amount}
}

func modifier(t *Thing) {
	t.Amount = 20
}

func modifier2(t Thing) {
	t.Amount = 30
}

func modifier3(t *Thing) {
	t.Amount = 100
}

func main() {
	myVar := NewThing("thing1", 10)
	fmt.Printf("myVar (ptr) %v %T\n", myVar, myVar)
	fmt.Printf("myVar (struct) %v %T\n", *myVar, *myVar)
	fmt.Printf("myVar (ptr address) %v %T\n", &myVar, &myVar)

modifier(myVar)
fmt.Println(myVar.Amount)

modifier2(*myVar)
fmt.Println(myVar.Amount)

t := Thing{"thing3", 1}
modifier3(&t)
fmt.Println(t.Amount)
}

e ya:

myVar (ptr) &{thing1 10} *main.Thing
myVar (struct) {thing1 10} main.Thing
myVar (ptr address) 0xc00004a020 **main.Thing
20
20
100

es mejor practica y el compilador te dará mejores resultados, código mas eficiente y rápido.

como digo siempre mirad el código de Go que suele estar bastante bien:
https://github.com/golang/go/blob/master/src/arena/arena.go
aqui usan un constructor por temas de proteger el campo privado...

aqui Element por ejemplo pues no:
https://github.com/golang/go/blob/master/src/container/list/list.go
de hecho al tener el *Element dentro hace que esto vaya a la heap... si hicieses **Element en la lista tendrías un puntero extra que no necesitas.

aqui el parser y formater, privados para que se usen solo dentro del paquete tar:
https://github.com/golang/go/blob/master/src/archive/tar/strconv.go
obviamente sin constructor

si necesitas el puntero el 99% de las veces, sera algo asi:
https://github.com/golang/go/blob/39ec246e739a787375b00acd92c10311863575a2/src/archive/tar/common.go#L647

por lo que he explicado del escapé-análisis este patron sera el mas común, xq el compilador casi siempre te rastreara donde se utiliza y te lo limpiaría, con el constructor esto no es posible por desgracia.
https://words.filippo.io/efficient-go-apis-with-the-inliner/

esto ya lo comparti en su dia. para que el compilador fuera inteligente con los constructores habría que declarar la variable e inicializarla asi.

func NewUnauthenticatedCipher(key, nonce []byte) (*Cipher, error) {
    var c Cipher
    return newCipher(&c, key, nonce)
}
2