Ci è un modo per leggere membri non esportate utilizzando riflettere
func read_foo(f *Foo) {
v := reflect.ValueOf(*f)
y := v.FieldByName("y")
fmt.Println(y.Interface())
}
Tuttavia, cercando di utilizzare y.Set, o comunque impostare il campo con la riflessione si tradurrà in codice panico che si' sta cercando di impostare un campo non esportato al di fuori del pacchetto.
In breve: i campi non esportati devono essere non esportati per un motivo, se è necessario modificarli o inserire l'elemento che deve modificarlo nello stesso pacchetto o esporre/esportare un modo sicuro per alterarlo.
Detto questo, nell'interesse di rispondere pienamente alla domanda, è possibile esegue questa
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
}
Questo è davvero, davvero pessima idea. Non è portabile, se mai cambierà dimensione, fallirà, se mai riordinerai l'ordine dei campi in Foo, cambi i loro tipi, o le loro dimensioni, o aggiungerai nuovi campi prima di quelli preesistenti questa funzione cambierà allegramente il nuova rappresentazione di dati casuali e senza senso. Penso anche che potrebbe rompere la garbage collection per questo blocco.
Per favore, se è necessario modificare un campo dall'esterno del pacchetto, scrivere la funzionalità per modificarlo dall'interno del pacchetto o esportarlo.
Edit: Ecco un modo un po 'più sicuro per farlo:
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
}
Questo è più sicuro, in quanto sarà sempre trovare il campo di nome corretto, ma è ancora ostile, probabilmente lento, e non sono sicuro se ha problemi con la raccolta dei rifiuti. Non mancherà di avvertirti se stai facendo qualcosa di strano (potresti rendere questo codice un piccolo un po 'più sicuro aggiungendo alcuni controlli, ma non lo infastidirò, in questo modo gli aspetti vanno abbastanza bene).
Ricordare inoltre che FieldByName è suscettibile allo sviluppatore del pacchetto che modifica il nome della variabile. Come sviluppatore di pacchetti, posso dirvi che non ho assolutamente problemi a cambiare il nome di cose che gli utenti dovrebbero ignorare. Potresti usare Field, ma in questo modo sei suscettibile allo sviluppatore di cambiare l'ordine dei campi senza alcun preavviso, il che è qualcosa che non mi fa nemmeno esitare a fare. Tieni presente che questa combinazione di riflessi e non sicuri è ... non sicura, a differenza delle normali modifiche al nome, questo non ti darà un errore in fase di compilazione. Invece, il programma verrà improvvisamente in preda al panico o farà qualcosa di strano e indefinito perché ha ottenuto il campo sbagliato, nel senso che anche se TU sei lo sviluppatore di pacchetti che ha cambiato il nome, potresti comunque non ricordarti ovunque hai fatto questo trucco e trascorri un po 'di tempo giù perché i tuoi test improvvisamente si sono interrotti perché il compilatore non si lamenta.Ho detto che questa è una cattiva idea?
Edit2: Dal momento che si parla di test white box, si noti che se il nome di un file nella directory <whatever>_test.go
non si compila a meno di utilizzare go test
, quindi se si vuole fare test white box, in cima dichiarare che package <yourpackage>
ti consentirà di accedere ai campi non esposti e, se desideri eseguire il test della scatola nera, utilizzerai package <yourpackage>_test
.
Se è necessario testare due pacchetti contemporaneamente, tuttavia, penso che si possa rimanere bloccati e potrebbe essere necessario rivedere il progetto.
Non si può biforcare la libreria esistente ed esporre i campi che è necessario modificare? (nota che dovresti presumere di non essere esposti per una buona ragione) – elithrar
@elithrar Tutto questo è il mio codice. Quindi ... sì, non sono esposti per una buona ragione; e sì, ho bisogno di accedervi. – Matt