2013-07-31 23 views
16

Mam struct w jednym pakiecie, który ma prywatne pola:W Go, czy istnieje sposób dostępu do prywatnych pól struktury z innego pakietu?

package foo 

type Foo struct { 
    x int 
    y *Foo 
} 

I kolejny pakiet (na przykład pakiet testowanie biało-box) potrzebuje dostępu do nich:

package bar 

import "../foo" 

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

Czy istnieje sposób zadeklarowania, że ​​bar jest rodzajem pakietu "przyjacielskiego" lub jakimkolwiek innym sposobem uzyskania dostępu do prywatnych użytkowników foo.Foo od bar, ale nadal zachowują je jako prywatne dla wszystkich innych pakietów (być może w wersji unsafe)?

+0

Nie można rozwidlić istniejącej biblioteki i odsłonić pola, które należy zmodyfikować? (zwróć uwagę, że powinieneś założyć, że są niewystawione z jakiegoś powodu) – elithrar

+0

@elithrar Wszystko to jest mój kod. Więc ... tak, są nienaświetleni z ważnego powodu; i tak, potrzebuję do nich dostępu. – Matt

Odpowiedz

28

Nie jest sposób czytać unexported członkowie użyciu odzwierciedlać

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

jednak próbuje użyć y.Set, lub w inny sposób ustawić pole z odzwierciedlają spowoduje kodu panikować to ty” próbujesz ustawić nieodnotowane pole poza pakietem.

W skrócie: niewytrawione pola powinny być niezawarte z jakiegoś powodu, jeśli trzeba je zmienić, należy umieścić coś, co trzeba zmienić w tym samym opakowaniu, lub wyeksponować/wyeksportować w bezpieczny sposób, aby je zmienić.

Powiedział, że w interesie pełni odpowiedzieć na pytanie, może zrobić

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 

} 

jest to naprawdę zły pomysł. Nie jest przenośny, jeśli kiedykolwiek zmieni rozmiar, to się nie powiedzie, jeśli kiedykolwiek zmienisz kolejność pól w Foo, zmienisz ich typy lub ich rozmiary, lub dodasz nowe pola przed wcześniejszymi, funkcja ta będzie wesoło zmieniać nowa reprezentacja przypadkowych danych z bełkotu bez informowania o tym. Myślę też, że może przerwać wyrzucanie śmieci dla tego bloku.

Jeśli chcesz zmienić pole spoza paczki, zapisz funkcję, aby zmienić ją z poziomu paczki lub wyeksportować.

Edycja: Oto nieco bezpieczniejszy sposób, aby to zrobić:

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 

} 

Jest to bezpieczniejsze, ponieważ będzie ona zawsze znaleźć odpowiednie pole o nazwie, ale to nadal nieprzyjazny, prawdopodobnie powolny, i nie jestem pewien, jeśli komunikuje się ze zbieraniem śmieci. Nie ostrzeże Cię również, jeśli robisz coś dziwnego (możesz uczynić ten kod bezpieczniejszym, dodając kilka sprawdzeń, ale nie będę się tym przejmował, to będzie dobrze zrozumiałe).

Należy również pamiętać, że nazwa FieldByName jest podatna na zmianę nazwy zmiennej przez programistę pakietów. Jako programista pakietów mogę powiedzieć, że nie mam absolutnie żadnych skrupułów, by zmienić nazwy rzeczy, których użytkownicy powinni być nieświadomi. Możesz użyć Field, ale wtedy jesteś podatny na dewelopera, zmieniając kolejność pól bez ostrzeżenia, co też nie mam zastrzeżeń. Należy pamiętać, że ta kombinacja refleksji i niebezpieczeństwa jest ... niebezpieczna, w przeciwieństwie do zwykłych zmian nazw, nie spowoduje to błędu podczas kompilacji. Zamiast tego program nagle wpadnie w panikę lub zrobi coś dziwnego i nieokreślonego, ponieważ ma niewłaściwe pole, co oznacza, że ​​nawet jeśli TY jesteś deweloperem pakietów, który zmienił nazwę, nadal możesz nie pamiętać wszędzie, gdzie zrobiłeś tę sztuczkę i spędzić trochę czasu na śledzeniu dlaczego testy nagle się zepsuły, ponieważ kompilator nie narzeka.Czy wspomniałem, że to zły pomysł?

Edit2: Skoro wspomniałeś testowanie białe pole, pamiętać, że jeśli nazwa pliku w katalogu <whatever>_test.go nie będzie kompilować, chyba że używasz go test, więc jeśli chcesz zrobić biały testowanie, na szczycie której deklarują package <yourpackage> da Ci dostęp do nieodnotowanych pól, a jeśli chcesz wykonać test czarnej skrzynki, użyj package <yourpackage>_test.

Jeśli chcesz przetestować dwie paczki w tym samym czasie, myślę, że możesz zostać zablokowany i może być konieczne ponowne przemyślenie projektu.

+0

Świetna odpowiedź, a +1000 na "[...] żadnych skrupułów, jeśli chodzi o zmianę nazw rzeczy, których użytkownicy powinni być nieświadomi". Z jakiegoś powodu jest niezbadany. – mna

+1

Dla jasności wszystkie te pakiety są moje, więc jeśli zmienię nazwę pola, będę o tym wiedział. Miałem dwa potencjalne zastosowania: test White Box, na którym zdecydowanie działa twoje rozwiązanie, ale także parser, który konwertuje łańcuchy znaków do obiektów w drugim pakiecie, którego efektywność skorzystałaby na ominięciu zwykłych konstruktorów dla struktur, ale twoje rozwiązanie pokonałby cel tego. Oczywiście mógłbym umieścić parser w tym samym pakiecie, ale chciałem, aby poszczególne części programu były oddzielne (może to nie jest bardzo podobne do Go?). – Matt

+4

Testowanie białego pudełka można łatwo zrobić, umieszczając pliki * _test.go w tym samym pakiecie, co testowany, dzięki czemu masz dostęp do nieodportowanych pól. Narzędzia Go poprawnie obsługują ten przypadek użycia i nie będą kompilować testów za pomocą kodu pakietu, z wyjątkiem sytuacji, gdy uruchamiane jest 'go test'. – mna