2013-07-31 13 views
16

ho una struct in un unico pacchetto che ha campi privati:In Go, c'è un modo per accedere ai campi privati ​​di una struct da un altro pacchetto?

package foo 

type Foo struct { 
    x int 
    y *Foo 
} 

E un altro pacchetto (ad esempio, un pacchetto di test white-box) ha bisogno di accedere ad essi:

package bar 

import "../foo" 

func change_foo(f *Foo) { 
    f.y = nil 
} 

Esiste un modo di dichiarare bar come una sorta di pacchetto "amico" o qualsiasi altro modo per poter accedere ai membri privati ​​di foo.Foo da bar, ma mantenerli comunque privati ​​per tutti gli altri pacchetti (forse qualcosa in unsafe)?

+0

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

+0

@elithrar Tutto questo è il mio codice. Quindi ... sì, non sono esposti per una buona ragione; e sì, ho bisogno di accedervi. – Matt

risposta

28

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.

+0

Ottima risposta e +1000 su "[...] non ci sono problemi a cambiare il nome di cose che gli utenti dovrebbero ignorare". Non è stato esportato per una ragione. – mna

+1

Giusto per essere chiari, tutti questi pacchetti sono miei, quindi se cambio il nome di un campo, vorrei saperlo. Avevo in mente due potenziali usi: il test della scatola bianca, per il quale la soluzione funziona sicuramente, ma anche un parser, che converte le stringhe negli oggetti nell'altro pacchetto, la cui efficienza trarrebbe vantaggio dall'esclusione dei soliti costruttori per le strutture ma la soluzione sconfiggerebbe lo scopo di ciò. Certo, potrei mettere il parser nello stesso pacchetto, ma volevo tenere separate le varie parti del programma (forse non è molto simile a Go?). – Matt

+4

La verifica dei riquadri bianchi viene eseguita facilmente inserendo i file * _test.go nello stesso pacchetto di quello in prova, in modo da poter accedere ai campi non esposti. Gli strumenti Go supportano correttamente questo caso d'uso e non compilano i test con il codice del pacchetto tranne quando si esegue 'go test'. – mna

Problemi correlati