tutorial - En Go, ¿hay alguna forma de acceder a los campos privados de una estructura desde otro paquete?
framework django python (2)
Estoy empezando con C ++ -> Ir a portar y me encontré con un par de clases que eran amigos entre sí. Estoy bastante seguro de que si forman parte del mismo paquete, son amigos por defecto, de manera efectiva.
La primera letra mayúscula para un identificador está vinculada dentro del paquete. Por lo tanto, pueden estar en archivos separados siempre que estén en el mismo directorio, y tendrán la capacidad de ver los campos no exportados de cada uno.
Usar reflect, incluso si se trata de Go stdlib, es algo en lo que debe pensar siempre con cuidado. Añade una gran cantidad de tiempo de ejecución. La solución sería básicamente copiar y pegar si los dos tipos de estructura que desea que sean amigos, simplemente deben estar en la misma carpeta. De lo contrario hay que exportarlos. (Personalmente, creo que el interés por el ''riesgo'' de exportar datos confidenciales es bastante exagerado, aunque si está escribiendo una biblioteca solitaria que no tiene ejecutables, puede que tenga algún sentido, ya que los usuarios de la biblioteca no los verán campos en el GoDoc y por lo tanto no creo que puedan depender de su existencia).
Tengo una estructura en un paquete que tiene campos privados:
package foo
type Foo struct {
x int
y *Foo
}
Y otro paquete (por ejemplo, un paquete de prueba de caja blanca) necesita acceso a ellos:
package bar
import "../foo"
func change_foo(f *Foo) {
f.y = nil
}
¿Hay alguna manera de declarar que bar
es una clase de paquete de "amigos" o cualquier otra manera de poder acceder a foo.Foo
miembros privados de foo.Foo
desde el bar
, pero aún así los mantienen privados para todos los demás paquetes (quizás algo unsafe
)? ?
Hay una manera de leer los miembros no exportados usando reflect
func read_foo(f *Foo) {
v := reflect.ValueOf(*f)
y := v.FieldByName("y")
fmt.Println(y.Interface())
}
Sin embargo, si intenta usar y.Set, o establezca de otro modo el campo con reflejo, se producirá el pánico en el código que está intentando configurar un campo no exportado fuera del paquete.
En resumen: los campos no exportados no deben ser exportados por una razón, si necesita modificarlos, ponga la cosa que necesita alterarla en el mismo paquete o exponga / exporte alguna forma segura de modificarla.
Dicho esto, con el fin de responder completamente a la pregunta, puedes hacer esto
func change_foo(f *Foo) {
// Since structs are organized in memory order, we can advance the pointer
// by field size until we''re at the desired member. For y, we advance by 8
// since it''s the size of an int on a 64-bit machine and the int "x" is first
// in the representation of Foo.
//
// If you wanted to alter x, you wouldn''t advance the pointer at all, and simply
// would need to convert ptrTof to the type (*int)
ptrTof := unsafe.Pointer(f)
ptrTof = unsafe.Pointer(uintptr(ptrTof) + uintptr(8)) // Or 4, if this is 32-bit
ptrToy := (**Foo)(ptrTof)
*ptrToy = nil // or *ptrToy = &Foo{} or whatever you want
}
Esta es una muy, muy mala idea. No es portátil, si la int cambia de tamaño, fallará, si alguna vez reorganiza el orden de los campos en Foo, cambia sus tipos o sus tamaños, o agrega nuevos campos antes de los existentes, esta función cambiará alegremente el Nueva representación a datos aleatorios sin avisar. También creo que podría romper la recolección de basura para este bloque.
Por favor, si necesita modificar un campo desde fuera del paquete, escriba la funcionalidad para cambiarlo dentro del paquete o expórtelo.
Edit: Aquí hay una manera un poco más segura de hacerlo:
func change_foo(f *Foo) {
// Note, simply doing reflect.ValueOf(*f) won''t work, need to do this
pointerVal := reflect.ValueOf(f)
val := reflect.Indirect(pointerVal)
member := val.FieldByName("y")
ptrToY := unsafe.Pointer(member.UnsafeAddr())
realPtrToY := (**Foo)(ptrToY)
*realPtrToY = nil // or &Foo{} or whatever
}
Esto es más seguro, ya que siempre encontrará el campo con el nombre correcto, pero aún así es hostil, probablemente lento, y no estoy seguro de si se mete con la recolección de basura. También dejará de advertirte si estás haciendo algo extraño (podrías hacer que este código sea un poco más seguro si agregas algunos controles, pero no me molestaré, esto es lo suficientemente claro).
También tenga en cuenta que FieldByName es susceptible de que el desarrollador del paquete cambie el nombre de la variable. Como desarrollador de paquetes, puedo decirle que no tengo ningún reparo en cambiar los nombres de las cosas que los usuarios no deberían tener en cuenta. Puedes usar Field, pero entonces eres susceptible a que el desarrollador cambie el orden de los campos sin avisar, lo cual es algo que tampoco tengo reparos en hacerlo. Tenga en cuenta que esta combinación de reflejo e inseguridad es ... insegura, a diferencia de los cambios de nombre normales, esto no le dará un error de compilación. En su lugar, el programa solo entrará en pánico o hará algo extraño e indefinido porque tiene el campo equivocado, lo que significa que incluso si TÚ eres el desarrollador del paquete que hizo el cambio de nombre, es posible que aún no recuerdes el lugar donde hiciste este truco y pases un rato por qué sus pruebas se rompieron de repente porque el compilador no se queja. ¿Mencioné que esto es una mala idea?
Edit2: Ya que menciona las pruebas de caja blanca, tenga en cuenta que si nombra un archivo en su directorio <whatever>_test.go
, no se compilará a menos que use go test
, así que si desea hacer pruebas de caja blanca, en la parte superior declare package <yourpackage>
que le dará acceso a los campos no exportados, y si desea realizar pruebas de caja negra, use el package <yourpackage>_test
.
Sin embargo, si necesita probar dos paquetes de caja blanca al mismo tiempo, creo que puede estar atascado y es posible que deba reconsiderar su diseño.