2013-07-31 14 views
16

Ich habe eine Struktur in einem Paket, die privaten Felder hat:In Go gibt es eine Möglichkeit, auf private Felder einer Struktur von einem anderen Paket zuzugreifen?

package foo 

type Foo struct { 
    x int 
    y *Foo 
} 

Und ein anderes Paket (zum Beispiel ein Testpaket White-Box) muss Zugang zu ihnen:

package bar 

import "../foo" 

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

Gibt es ein Art und Weise zu erklären, bar eine Art von "Freund" -Paket oder eine andere Möglichkeit zu sein, auf foo.Foo 's private Mitglieder von bar zugreifen, aber immer noch privat für alle anderen Pakete (vielleicht etwas in unsafe)?

+0

Können Sie die vorhandene Bibliothek nicht forchieren und die Felder offen legen, die Sie ändern müssen? (Beachten Sie, dass Sie davon ausgehen sollten, dass sie aus einem guten Grund unbelichtet sind) – elithrar

+0

@elithrar Alles ist mein Code. Also ... ja, sie sind aus einem guten Grund nicht ausgesetzt; und ja, ich muss auf sie zugreifen. – Matt

Antwort

28

Es ist ein Weg, um lesen unexported Mitglieder

func read_foo(f *Foo) { 
    v := reflect.ValueOf(*f) 
    y := v.FieldByName("y") 
    fmt.Println(y.Interface()) 
} 

jedoch versuchen y.Set zu verwenden oder auf andere Weise setzen Sie das Feld reflektieren Verwendung mit in dem Code, den Sie in Panik zu geraten führt reflektieren‘ Versuchen Sie, ein nicht exportiertes Feld außerhalb des Pakets zu setzen.

Kurz gesagt: Nicht exportierte Felder sollten aus einem bestimmten Grund nicht exportiert werden. Wenn Sie sie ändern müssen, legen Sie entweder das Objekt in das gleiche Paket oder exponieren/exportieren Sie einen sicheren Weg, um es zu ändern.

Das heißt, im Interesse der Frage vollständig zu beantworten, Sie können tun, um diese

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 

} 

Dies ist ein wirklich, wirklich schlechte Idee. Es ist nicht portierbar, wenn die Größe jemals geändert wird, wird es scheitern, wenn Sie die Reihenfolge der Felder in Foo ändern, ihre Typen oder ihre Größe ändern oder neue Felder vor den bereits bestehenden hinzufügen, wird diese Funktion die Änderungen munter ändern neue Darstellung zu zufälligen Kauderwelsch-Daten ohne es Ihnen zu sagen. Ich denke auch, dass es Müllsammlung für diesen Block brechen könnte.

Wenn Sie ein Feld außerhalb des Pakets ändern müssen, schreiben Sie entweder die Funktionalität, um es im Paket zu ändern, oder exportieren Sie es.

Edit: Hier ist ein wenig sicherer Weg, es zu tun:

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 

} 

Dies ist sicherer, da es immer den richtigen Namen Feld finden, aber es ist immer noch unfreundlich, wahrscheinlich langsam, und ich bin nicht sicher, wenn es mit der Müllsammlung zu tun hat. Es wird Sie auch nicht warnen, wenn Sie etwas komisches machen (Sie könnten diesen Code zu einem kleinen sicherer machen, indem Sie ein paar Checks hinzufügen, aber ich werde mich nicht darum kümmern, das bringt den Kern gut genug).

Denken Sie auch daran, dass FieldByName für den Paketentwickler anfällig ist, den Namen der Variablen zu ändern. Als Paketentwickler kann ich Ihnen sagen, dass ich absolut keine Bedenken habe, die Namen von Dingen zu ändern, die den Benutzern nicht bewusst sein sollten. Sie könnten Field verwenden, aber dann sind Sie anfällig dafür, dass der Entwickler die Reihenfolge der Felder ohne Warnung ändert, was ich auch nicht befürchte. Denken Sie daran, dass diese Kombination von "reflect" und "unsafe" nicht sicher ist. Anders als bei normalen Namensänderungen erhalten Sie dadurch keinen Fehler bei der Kompilierung. Stattdessen wird das Programm plötzlich in Panik geraten oder etwas Seltsames und Undefiniertes tun, weil es das falsche Feld hat. Das heißt, selbst wenn du der Paketentwickler bist, der den Namen geändert hat, erinnerst du dich vielleicht nicht überall wo du diesen Trick gemacht hast wieso sind deine Tests plötzlich kaputt, weil der Compiler sich nicht beschwert.Habe ich erwähnt, dass dies eine schlechte Idee ist?

Edit2: Da Sie White Box Tests erwähnen, beachten Sie, dass, wenn Sie eine Datei in Ihrem Verzeichnis <whatever>_test.go Namen wird es nicht kompilieren, wenn Sie go test verwenden, wenn Sie also weißen Box-Test machen wollen, an der Spitze package <yourpackage> erklären die gibt Ihnen Zugriff auf nicht-exportierte Felder, und wenn Sie Black-Box-Tests durchführen möchten, verwenden Sie package <yourpackage>_test.

Wenn Sie in der gleichen Zeit zu White-Box-Test zwei Pakete benötigen, jedoch denke ich, dass Sie nicht mehr sein können, und müssen möglicherweise Ihr Design zu überdenken.

+0

Große Antwort, und +1000 auf "[...] keine Skrupel über die Änderung der Namen von Dingen, die Benutzer nicht wissen sollten". Es wird aus einem Grund nicht gemeldet. – mna

+1

Nur um klar zu sein, all diese Pakete sind meine eigenen, also wenn ich den Namen eines Feldes ändere, würde ich davon wissen. Ich habe zwei mögliche Anwendungen im Sinn: White-Box-Tests, für die Ihre Lösung definitiv geeignet ist, aber auch einen Parser, der Strings in die Objekte des anderen Pakets konvertiert, dessen Effizienz von der Umgehung der üblichen Konstruktoren für die Strukturen, aber Ihre Lösung profitieren würde würde den Zweck davon besiegen. Natürlich könnte ich den Parser in dasselbe Paket einfügen, aber ich wollte die verschiedenen Teile des Programms getrennt halten (vielleicht ist das nicht sehr Go-ähnlich?). – Matt

+4

White-Box-Tests können einfach durchgeführt werden, indem Sie Ihre * _test.go-Dateien in das gleiche Paket wie das zu testende eingeben, sodass Sie Zugriff auf nicht-exportierte Felder haben. Die Go-Tools unterstützen diesen Anwendungsfall korrekt und kompilieren Ihre Tests nicht mit Ihrem Paketcode, außer wenn Sie "go test" ausführen. – mna