Image is generated By gopherize.me. Artwork by Ashley McNamara inspired by Renee French. Web app by Mat Ryer
この記事はGo1.18連載の4記事目です。
はじめに
こんにちは、TIG/DXユニット所属の宮永です。
本記事ではGo1.18で追加されたnet/netipの機能について解説します。
net/netipとは?
IPアドレスを対象とした基本的な操作(比較演算子による操作など)を提供するパッケージです。
netipパッケージに定義されている型は全部で3つ「Addr型(IPアドレスを定義)」「AddrPort型(IPアドレスとポートを定義)」「Prefix型(IPアドレスとビット長を定義)」です。
net/netipによって新たに導入された「Addr型」は「net.IP型」と比較してより少ないメモリでimmutableでかつ比較演算子を使って簡単に比較できるようになっていると公式のdocsには記載されています。
net/netipパッケージの導入提案はBradさんによってこちらのissueでされています。
私はnet.IPを使用したことはないのですが、従来のnetパッケージにはnet.IP型が実装されていました。net/netipの導入に伴ってnet.IP型の取り扱いやパッケージの命名方法などはかなり議論されていたようです。
議論の争点は
- netパッケージに軽量なアドレスパッケージを追加するか?
- 汎用的なIPアドレス操作を担うパッケージを追加するか?
の2点でした(1)を採用する場合は今回追加されるパッケージ名は「net/netip」ではなく「net/netaddr」になっていたようです。
結論としてはnet/netipとして汎用的なIPアドレス操作を担うパッケージとしてgo1.18に取り入れることが決定したようです。
net/netipの利用用途は?
私自身、netパッケージはnet/http程度しか触ったことがないため従来のnet.IP型がどのように利用されていたのか、またどんなところに欠点があったのかを知りません。
net/netipの追加に大きな貢献をしたBradさんの技術ブログnetaddr.IP: a new IP address type for Go · Tailscaleにnet/netipの前身であるinetaf/netaddrを作成した経緯が記載されていましたのでこちらをベースにnet/netipの紹介をします。
Bradさんの記事では従来のnet.IP型の問題点はnet.IP型が単なるbyteのスライスでしか定義されていないことであると指摘されています。
この問題は、例えばIPアドレスを比較する際に==
などの演算子は使用できないということを意味しています。
また、IPアドレスを表現するには不要な24バイトがnet.IPに割り当てられていることを指摘しています。
確かにgo1.17のnet.IP型を見てみると明確なサイズ制限はされていません。
// IP address lengths (bytes). |
一方でgo1.18beta2のnetip.Addrを確認するとaddr
にuint128
が明確に定義されています。
type Addr struct { |
goにはuint128
という型は存在しないため、uint64型2つを使用して定義しています。
// uint128 represents a uint128 using two uint64s. |
uint64
とは64ビット、つまりuint128
で128ビット(=16バイト)を表現しています。
また、Addr型にはIPv6のゾーン識別子としてzというフィールドを用意しています。
// z0, z4, and z6noz are sentinel IP.z values. |
ゾーンを参照するZone()
メソッドも用意されています。
// Zone returns ip's IPv6 scoped addressing zone, if any. |
ゾーンを定義する際にはWithZone()
メソッドを使用します。
// WithZone returns an IP that's the same as ip but with the provided |
WithZone()
メソッドは文字列からAddr型を定義するParseAddr()
メソッドの内部でも利用されています。
// parseIPv6 parses s as an IPv6 address (in form "2001:db8::68"). |
net/netipを使う
それではnet/netipパッケージを実際に使ってみます。
まずは文字列からAddr型を生成します。ip0
を空文字として不当なAddr型に、ip1
をゾーン識別子(%eth0)付きのAddr型として設定します。
Addr型のメソッドであるIsValid()
メソッドを使用していそれぞれの入力を評価します。
package main |
以上のソースコードを実行すると
false |
が出力されます。
それでは次にIPv6アドレス(ゾーン識別子付き)を定義して比較演算子を使用してみます。
package main |
以上のコードを実行すると
true |
と出力されます。net/netip導入の1つの目標である演算子による比較が可能になっています。
また、IPv6の表記ではゼロが2度以上続く場合「::」
として省略できます。
省略せずに展開するメソッドとしてStringExpanded()
などのメソッドも用意されています。StringExpanded()
で返却される値は文字列です。
package main |
fe80:0000:0000:0000:0000:0000:0000:0002%eth0 |
ビット長もBitLen()
メソッドを使えば簡単に調べることができます。
package main |
128 |
簡単にIPアドレスの操作を行うことができますね。
まとめ
- net/netipによって新たに導入された構造体は「Addr型(IPアドレスを定義)」「AddrPort型(IPアドレスとポートを定義)」「Prefix型(IPアドレスとビット長を定義)」の3つである。
- net/netipではとnet.IPと比較してより少ないメモリでimmutableでかつ比較演算子を使って簡単に比較できるようになった。
今回Go1.18の集中連載記事を書くにあたってGoの公式リポジトリのissueやdocsなどを比較しながらまとめました。普段の実装では本家のソースコードをつぶさに確認することはなかったので良い体験ができたと思っています。「Goの実装をより良くするにはGo自体の実装を研究することだ」と誰かが言っていたのを思い出しました。
これを機に自分の普段の実装も見直してみたいと思います。
最後までお付き合いいただきありがとうございました。