Przykład na to, że w języku programowania zawsze można coś ulepszyć.
Zapotrzebowanie
Pisząc aplikację sieciową zachodzi częsta konieczność pracy tak z pojedynczymi adresami IP jak i zestawami adresów IP a do tego dochodzi konieczność różnych manipulacji na tych danych. W języku Go służy do tego net.IP zawarty w bibliotece standardowej Go. Dla sieci można się zaś posłużyć net.IPNet. Tailsacle, dostawca usługi VPN, korzystający z Go, stwierdził jednak że domyślne rozwiązanie zawarte w tym języku programowania go nie zadowala. Napisali więc nowy pakiet inet.af/netaddr
(dostępny na ich GitHubie) zawierający nowy typ dla adresu IP.
Co im się nie podobało w net.IP?
Na liście skarg, zażaleń i wniosku znalazły się m.in. następujące zarzuty względem net.IP:
- Jest to typ mutujący. Podstawowy typ net.IP to po prostu
byte[]
co oznacza, że wszystko, do czego go przekażesz, może go zmodyfikować. Tymczasem niezmienne struktury danych są bezpieczniejsze i łatwiejsze w użyciu. - Nie jest porównywalny, nie wspiera operatora
==
i nie może być używany jako klucz mapy. - Jest duży. Adres IP w net.IP Go składa się z dwóch części: 24-bajtowego nagłówka segmentu, a także 4 lub 16 bajtów adresu IP. Jeśli chcesz mieć strefę IPv6, musisz użyć net.IPAddr z 16-bajtowym nagłówkiem.
Celem prac było więc przede wszystkim zapewnienie aby nowy typ był niemutowalny, porównywalny i zajmował możliwie mało pamięci.
Prace trwały
Tailscale zaczęło od:
type IP struct {
Addr [16]byte
}
Zapewnili niemutowalność, porównywalność i mały rozmiar ale nadal nie było rozróżnienia IPv4 od IPv6 i wsparcia stref IPv6. No i nie osiągnięto interoperacyjności ze standardową bibliotekę Go. Korzystając z tego, że interfejsy w tym języku są porównywalne, spróbowano z:
type IP struct {
ipImpl
}
type ipImpl interface {
is4() bool
is6() bool
String() string
}
type v4Addr [4]byte
type v6Addr [16]byte
type v6AddrZone struct {
v6Addr
zone string
}
Lepiej ale nie idealnie – nadal brak było chociażby współpracy ze standard library.
Sprawdź oferty pracy na TeamQuest
Wskaźnik na ratunek
W końcu zdecydowano się użyć wskaźnika:
type IP struct {
addr [16]byte
zoneAndFamily *T
}
var (
z0 *T // nil for the zero value
z4 = new(T) // sentinel value to mean IPv4
z6 = new(T) // sentinel value to mean IPv6 with no zone
)
Ostatecznie powstał pakiet go4.org/intern
zawierający całą brudną robotę (niejako backend tego nowego typu) tak aby pakiet inet.af/netaddr
mógł wyglądać schludniej. Później były dalsze zmiany, np. zamiast addr [16]byte
użyto parę wartości uint64
. Obecnie inet.af/netaddr
zawiera:
- IPPort: typ wartości dla adresu IP i portu
- IPPrefix: typ wartości dla adresu IP i prefiks CIDR (np. 192.168.0.1/16)
- IPRange: typ wartości dla zakresu adresów IP (np. 10.0.0.200-100.0.0.255)
- IPSet: wydajny, niezmienny zestaw adresów IP, zbudowany za pomocą IPSetBuilder.