Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16054] ファイルの概要

このコミットは、Go言語の標準ライブラリであるnetパッケージにおけるネットワーク接続の確立(ダイヤル)に関するAPIの重要な変更を扱っています。具体的には、以前導入されたDialOpt関数とDialOptionインターフェースを削除し、代わりにDialer構造体を導入することで、ダイヤルオプションの指定方法をより柔軟かつGoらしい(idiomatic)方法に改善しています。

コミット

commit 751a24e86e0044b75c075c21b13dce2db9a1f744
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Apr 2 13:24:16 2013 -0700

    net: delete DialOpt and DialOption; add struct Dialer
    
    Per discussions on golang-nuts and golang-dev:
    "Some concerns with DialOpt"
    https://groups.google.com/d/msg/golang-nuts/Hfh9aqhXyUw/W3uYi8lOdKcJ
    https://groups.google.com/d/msg/golang-dev/37omSQeWv4Y/KASGIfPpXh0J
    
    R=golang-dev, google, r
    CC=golang-dev
    https://golang.org/cl/8274043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/751a24e86e0044b75c075c21b13dce2db9a1f744

元コミット内容

net: delete DialOpt and DialOption; add struct Dialer

このコミットは、netパッケージからDialOpt関数とDialOptionインターフェースを削除し、代わりにDialer構造体を追加します。これはgolang-nutsおよびgolang-devメーリングリストでの議論に基づいています。

変更の背景

Go 1.1のリリースに向けて、netパッケージのDial関数にオプションを渡すための新しいメカニズムが検討されていました。当初、DialOpt関数とDialOptionインターフェースが導入されました。しかし、この設計にはいくつかの懸念が表明されました。

主な懸念点は以下の通りです。

  1. 柔軟性の欠如: DialOptionインターフェースは、各オプションがsetDialOptメソッドを実装することを要求していました。これにより、新しいオプションを追加するたびに新しい型とメソッドを定義する必要があり、柔軟性に欠けるという意見がありました。特に、複数のオプションを組み合わせる際に冗長になる可能性がありました。
  2. Goらしい設計からの逸脱: Goの標準ライブラリでは、オプションを渡す一般的なパターンとして、構造体や可変長引数(variadic arguments)を使用することが多いです。DialOptionインターフェースの設計は、これらの一般的なパターンから逸脱していると見なされました。
  3. 将来的な拡張性: DialOptionインターフェースの設計では、将来的に新しいダイヤルオプションを追加する際に、既存のインターフェースを変更する必要が生じる可能性があり、後方互換性の維持が困難になることが懸念されました。

これらの議論の結果、よりGoらしい設計であり、柔軟性と拡張性に優れたDialer構造体を使用するアプローチが提案され、このコミットで採用されました。Dialer構造体は、ダイヤルに関連するすべてのオプション(タイムアウト、デッドライン、ローカルアドレスなど)を単一の構造体にまとめることで、よりクリーンで管理しやすいAPIを提供します。

前提知識の解説

Go言語のnetパッケージ

netパッケージは、Go言語におけるネットワークI/Oの基本的な機能を提供します。TCP/IP、UDP、Unixドメインソケットなどのネットワークプロトコルを扱うためのインターフェースや関数が含まれています。

ネットワークダイヤル(Dialing)

ネットワークダイヤルとは、クライアントがサーバーに接続を確立するプロセスを指します。Go言語のnetパッケージでは、Dial関数がこの目的で使用されます。Dial関数は、指定されたネットワーク(例: "tcp")とアドレス(例: "localhost:8080")に対して接続を試み、成功すればnet.Connインターフェースを実装する接続オブジェクトを返します。

DialOptDialOption (削除されたAPI)

  • DialOpt関数: 以前の設計で導入された関数で、Dial関数にオプションを適用するために使用されました。
  • DialOptionインターフェース: DialOpt関数に渡されるオプションを表すインターフェースでした。各オプション(例: タイムアウト、ローカルアドレス)はこのインターフェースを実装し、setDialOptメソッドを通じて内部のオプション構造体(dialOpts)を設定する役割を担っていました。

Dialer構造体 (新しいAPI)

Dialer構造体は、ネットワーク接続を確立する際の様々なオプション(タイムアウト、デッドライン、ローカルアドレスなど)をカプセル化するための新しい型です。この構造体のフィールドを直接設定することで、柔軟にダイヤル動作をカスタマイズできます。Dialer構造体は、そのゼロ値(フィールドが初期化されていない状態)でも有効であり、その場合はDial関数を直接呼び出すのと同等の動作をします。

  • Timeout: 接続が完了するまでの最大待機時間。
  • Deadline: 接続が失敗する絶対的な時刻。
  • LocalAddr: 接続に使用するローカルアドレス。

技術的詳細

このコミットの主要な変更は、ダイヤルオプションの管理方法をインターフェースベースのアプローチから構造体ベースのアプローチへと移行した点にあります。

DialOptionインターフェースとdialOpts構造体の削除

以前の設計では、DialOptionインターフェースと、そのオプションを保持する内部構造体dialOptsが存在しました。DialOptionを実装する各型(例: dialNetwork, dialDeadline, dialTimeoutOpt, localAddrOption)は、setDialOpt(*dialOpts)メソッドを通じてdialOptsのフィールドを設定していました。このアプローチは、オプションの追加ごとに新しい型とメソッドの定義が必要であり、コードの冗長性や拡張性の課題がありました。

Dialer構造体の導入

新しい設計では、Dialerという公開された構造体が導入されました。この構造体は、ダイヤルに関連するすべてのオプションを直接フィールドとして持ちます。

type Dialer struct {
	// Timeout is the maximum amount of time a dial will wait for
	// a connect to complete. If Deadline is also set, it may fail
	// earlier.
	//
	// The default is no timeout.
	//
	// With or without a timeout, the operating system may impose
	// its own earlier timeout. For instance, TCP timeouts are
	// often around 3 minutes.
	Timeout time.Duration

	// Deadline is the absolute point in time after which dials
	// will fail. If Timeout is set, it may fail earlier.
	// Zero means no deadline, or dependent on the operating system
	// as with the Timeout option.
	Deadline time.Time

	// LocalAddr is the local address to use when dialing an
	// address. The address must be of a compatible type for the
	// network being dialed.
	// If nil, a local address is automatically chosen.
	LocalAddr Addr
}

この構造体は、Dialメソッドを持ち、このメソッドが実際の接続処理を行います。

func (d *Dialer) Dial(network, address string) (Conn, error) {
	return resolveAndDial(network, address, d.LocalAddr, d.deadline())
}

Dialer構造体には、TimeoutDeadlineの両方が設定された場合に、より早い方の時刻を返すdeadline()ヘルパーメソッドも追加されています。

func (d *Dialer) deadline() time.Time {
	if d.Timeout == 0 {
		return d.Deadline
	}
	timeoutDeadline := time.Now().Add(d.Timeout)
	if d.Deadline.IsZero() || timeoutDeadline.Before(d.Deadline) {
		return timeoutDeadline
	} else {
		return d.Deadline
	}
}

DialおよびDialTimeout関数の変更

既存のnet.Dial関数とnet.DialTimeout関数も、内部的に新しいDialer構造体を使用するように変更されました。

  • net.Dial(network, address string): 内部でDialerのゼロ値を作成し、そのDialメソッドを呼び出すように変更されました。
  • net.DialTimeout(network, address string, timeout time.Duration): 内部でTimeoutフィールドが設定されたDialer構造体を作成し、そのDialメソッドを呼び出すように変更されました。

これにより、既存のAPIの互換性を保ちつつ、内部実装がDialer構造体ベースに統一されました。

dial_gen.goの追加とプラットフォーム固有の変更

src/pkg/net/dial_gen.goという新しいファイルが追加されました。このファイルには、resolveAndDialChannelという関数が含まれています。これは、deadlineがポーリングサーバーにプッシュダウンされていないオペレーティングシステム(Plan 9や一部の古いWindowsバージョン)で使用される、比較的非効率なゴルーチン競合ベースの実装です。

また、fd_plan9.go, fd_unix.go, fd_windows.goといったプラットフォーム固有のファイルも変更され、dialTimeout関数がresolveAndDial関数に置き換えられ、新しいDialer構造体と連携するように修正されました。これにより、各プラットフォームでのダイヤル処理が新しいAPI設計に適合するようになりました。

コアとなるコードの変更箇所

doc/go1.1.html

Go 1.1のドキュメントが更新され、netパッケージの変更点が反映されています。DialOptDialOptionの言及が削除され、代わりにDialer型が導入されたことが記述されています。

--- a/doc/go1.1.html
+++ b/doc/go1.1.html
@@ -779,16 +779,9 @@ Since this API change fixes a bug, it is permitted by the Go 1 compatibility rul
 </li>
 
 <li>
-The <a href="/pkg/net/"><code>net</code></a> package includes a new function,
-<a href="/pkg/net/#DialOpt"><code>DialOpt</code></a>, to supply options to
-<a href="/pkg/net/#Dial"><code>Dial</code></a>.
-Each option is represented by a new
-<a href="/pkg/net/#DialOption"><code>DialOption</code></a> interface.
-The new functions
-<a href="/pkg/net/#Deadline"><code>Deadline</code></a>,
-<a href="/pkg/net/#Timeout"><code>Timeout</code></a>,
-<a href="/pkg/net/#Network"><code>Network</code></a>, and
-<a href="/pkg/net/#LocalAddress"><code>LocalAddress</code></a> return a <code>DialOption</code>.
+The <a href="/pkg/net/"><code>net</code></a> package includes a new type,
+<a href="/pkg/net/#Dialer"><code>Dialer</code></a>, to supply options to
+<a href="/pkg/net/#Dialer.Dial"><code>Dial</code></a>.
 </li>
 
 <li>

src/pkg/net/dial.go

このファイルが最も大きく変更されています。

  • DialOptionインターフェース、dialOpts構造体、およびそれに関連するヘルパー関数(Network, Deadline, Timeout, LocalAddressなど)がすべて削除されました。
  • 新しいDialer構造体が定義され、Timeout, Deadline, LocalAddrフィールドを持ちます。
  • Dialer構造体のメソッドとしてdeadline()が追加されました。
  • Dial関数とDialTimeout関数が、内部でDialer構造体を使用するように変更されました。特にDial関数は、Dialerのゼロ値を作成してそのDialメソッドを呼び出すようになりました。
--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -9,112 +9,48 @@ import (
 	"time"
 )
 
-// A DialOption modifies a DialOpt call.
-type DialOption interface {
-	setDialOpt(*dialOpts)
-}
-
-var noLocalAddr Addr // nil
-
-// dialOpts holds all the dial options, populated by a DialOption's
-// setDialOpt.
+// A Dialer contains options for connecting to an address.
 //
-// All fields may be their zero value.
-type dialOpts struct {
-	deadline        time.Time
-	localAddr       Addr
-	network         string // if empty, "tcp"
-	deferredConnect bool
-}
-
-func (o *dialOpts) net() string {
-	if o.network == "" {
-		return "tcp"
+// The zero value for each field is equivalent to dialing
+// without that option. Dialing with the zero value of Dialer
+// is therefore equivalent to just calling the Dial function.
+type Dialer struct {
+	// Timeout is the maximum amount of time a dial will wait for
+	// a connect to complete. If Deadline is also set, it may fail
+	// earlier.
+	//
+	// The default is no timeout.
+	//
+	// With or without a timeout, the operating system may impose
+	// its own earlier timeout. For instance, TCP timeouts are
+	// often around 3 minutes.
+	Timeout time.Duration
+
+	// Deadline is the absolute point in time after which dials
+	// will fail. If Timeout is set, it may fail earlier.
+	// Zero means no deadline, or dependent on the operating system
+	// as with the Timeout option.
+	Deadline time.Time
+
+	// LocalAddr is the local address to use when dialing an
+	// address. The address must be of a compatible type for the
+	// network being dialed.
+	// If nil, a local address is automatically chosen.
+	LocalAddr Addr
+}
+
+// Return either now+Timeout or Deadline, whichever comes first.
+// Or zero, if neither is set.
+func (d *Dialer) deadline() time.Time {
+	if d.Timeout == 0 {
+		return d.Deadline
 	}
-	return o.network
-}
-
-var (
-	// TCP is a dial option to dial with TCP (over IPv4 or IPv6).
-	TCP = Network("tcp")
-
-	// UDP is a dial option to dial with UDP (over IPv4 or IPv6).
-	UDP = Network("udp")
-)
-
-// Network returns a DialOption to dial using the given network.
-//
-// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only),
-// "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4"
-// (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and
-// "unixpacket".
-//
-// For IP networks, net must be "ip", "ip4" or "ip6" followed
-// by a colon and a protocol number or name, such as
-// "ipv4:1" or "ip6:ospf".
-func Network(net string) DialOption {
-	return dialNetwork(net)
-}
-
-type dialNetwork string
-
-func (s dialNetwork) setDialOpt(o *dialOpts) {
-	o.network = string(s)
-}
-
-// Deadline returns a DialOption to fail a dial that doesn't
-// complete before t.
-func Deadline(t time.Time) DialOption {
-	return dialDeadline(t)
-}
-
-type dialDeadline time.Time
-
-func (t dialDeadline) setDialOpt(o *dialOpts) {
-	o.deadline = time.Time(t)
-}
-
-// Timeout returns a DialOption to fail a dial that doesn't
-// complete within the provided duration.
-func Timeout(d time.Duration) DialOption {
-	return dialTimeoutOpt(d)
-}
-
-type dialTimeoutOpt time.Duration
-
-func (d dialTimeoutOpt) setDialOpt(o *dialOpts) {
-	o.deadline = time.Now().Add(time.Duration(d))
-}
-
-type tcpFastOpen struct{}
-
-func (tcpFastOpen) setDialOpt(o *dialOpts) {
-	o.deferredConnect = true
-}
-
-// TODO(bradfitz): implement this (golang.org/issue/4842) and unexport this.
-//
-// TCPFastTimeout returns an option to use TCP Fast Open (TFO) when
-// doing this dial. It is only valid for use with TCP connections.
-// Data sent over a TFO connection may be processed by the peer
-// multiple times, so should be used with caution.
-func todo_TCPFastTimeout() DialOption {
-	return tcpFastOpen{}
-}
-
-type localAddrOption struct {
-	la Addr
-}
-
-func (a localAddrOption) setDialOpt(o *dialOpts) {
-	o.localAddr = a.la
-}
-
-// LocalAddress returns a dial option to perform a dial with the
-// provided local address. The address must be of a compatible type
-// for the network being dialed.
-func LocalAddress(addr Addr) DialOption {
-	return localAddrOption{addr}
-}
-
+	timeoutDeadline := time.Now().Add(d.Timeout)
+	if d.Deadline.IsZero() || timeoutDeadline.Before(d.Deadline) {
+		return timeoutDeadline
+	} else {
+		return d.Deadline
+	}
+}
+
 func parseNetwork(net string) (afnet string, proto int, err error) {
@@ -161,32 +97,33 @@ func resolveAddr(op, net, addr string, deadline time.Time) (Addr, error) {
 	return resolveInternetAddr(afnet, addr, deadline)
 }
 
-// Dial connects to the address addr on the network net.
+// Dial connects to the address on the named network.
 //
 // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only),
 // "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4"
 // (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and
 // "unixpacket".
@@ -194,32 +131,33 @@ func resolveAddr(op, net, addr string, deadline time.Time) (Addr, error) {
 // Examples:
 //	Dial("tcp", "localhost:8080")
 //	Dial("tcp", "192.0.2.1:80")
-//	Dial("tcp", "[2001:db8::1]:http")
-//	Dial("tcp", "[fe80::1%lo0]:80")
+//	Dial("tcp", "[2001:db8::1]:http")
+//	Dial("tcp", "[fe80::1%lo0]:80")
 //
-// For IP networks, the net must be "ip", "ip4" or "ip6" followed by a
-// colon and a protocol number or name and the addr must be a literal
-// IP address.\n+// For IP networks, the network must be "ip", "ip4" or "ip6" followed
+// by a colon and a protocol number or name and the addr must be a
+// literal IP address.
 //
 // Examples:
 //	Dial("ip4:1", "127.0.0.1")
 //	Dial("ip6:ospf", "::1")
 //
-// For Unix networks, the addr must be a file system path.
-func Dial(net, addr string) (Conn, error) {
-	return DialOpt(addr, dialNetwork(net))\n+// For Unix networks, the address must be a file system path.
+func Dial(network, address string) (Conn, error) {
+	var d Dialer
+	return d.Dial(network, address)
+}
+
+// DialTimeout acts like Dial but takes a timeout.
+// The timeout includes name resolution, if required.
+func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
+	d := Dialer{Timeout: timeout}
+	return d.Dial(network, address)
+}
+
+// Dial connects to the address on the named network.
+//
+// See func Dial for a description of the network and address
+// parameters.
+func (d *Dialer) Dial(network, address string) (Conn, error) {
+	return resolveAndDial(network, address, d.LocalAddr, d.deadline())
 }
 
-// DialOpt dials addr using the provided options.
-// If no options are provided, DialOpt(addr) is equivalent
-// to Dial("tcp", addr). See Dial for the syntax of addr.
-func DialOpt(addr string, opts ...DialOption) (Conn, error) {
-	var o dialOpts
-	for _, opt := range opts {
-		opt.setDialOpt(&o)
-	}
-	ra, err := resolveAddr("dial", o.net(), addr, o.deadline)
-	if err != nil {
-		return nil, err
-	}
-	return dial(o.net(), addr, o.localAddr, ra, o.deadline)
-}
-
 func dial(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) {
@@ -235,58 +172,6 @@ func dial(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error)
 	return
 }
 
-// DialTimeout acts like Dial but takes a timeout.
-// The timeout includes name resolution, if required.
-func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
-	return dialTimeout(net, addr, timeout)
-}
-
-// dialTimeoutRace is the old implementation of DialTimeout, still used
-// on operating systems where the deadline hasn't been pushed down
-// into the pollserver.
-// TODO: fix this on plan9.
-func dialTimeoutRace(net, addr string, timeout time.Duration) (Conn, error) {
-	t := time.NewTimer(timeout)
-	defer t.Stop()
-	type pair struct {
-		Conn
-		error
-	}
-	ch := make(chan pair, 1)
-	resolvedAddr := make(chan Addr, 1)
-	go func() {
-		ra, err := resolveAddr("dial", net, addr, noDeadline)
-		if err != nil {
-			ch <- pair{nil, err}
-			return
-		}
-		resolvedAddr <- ra // in case we need it for OpError
-		c, err := dial(net, addr, noLocalAddr, ra, noDeadline)
-		ch <- pair{c, err}
-	}()
-	select {
-	case <-t.C:
-		// Try to use the real Addr in our OpError, if we resolved it
-		// before the timeout. Otherwise we just use stringAddr.
-		var ra Addr
-		select {
-		case a := <-resolvedAddr:
-			ra = a
-		default:
-			ra = &stringAddr{net, addr}
-		}
-		err := &OpError{
-			Op:   "dial",
-			Net:  net,
-			Addr: ra,
-			Err:  &timeoutError{},
-		}
-		return nil, err
-	case p := <-ch:
-		return p.Conn, p.error
-	}
-}
-
 type stringAddr struct {
 	net, addr string
 }

src/pkg/net/dial_gen.go (新規ファイル)

このファイルは新しく追加され、resolveAndDialChannel関数を定義しています。これは、特定のオペレーティングシステム(Plan 9や一部の古いWindows)でタイムアウト処理を行うための、ゴルーチンとチャネルを使用した実装です。

--- /dev/null
+++ b/src/pkg/net/dial_gen.go
@@ -0,0 +1,61 @@
+// Copyright 2012 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows plan9
+
+package net
+
+import (
+	"time"
+)
+
+// resolveAndDialChannel is the simple pure-Go implementation of
+// resolveAndDial, still used on operating systems where the deadline
+// hasn't been pushed down into the pollserver. (Plan 9 and some old
+// versions of Windows)
+func resolveAndDialChannel(net, addr string, localAddr Addr, deadline time.Time) (Conn, error) {
+	timeout := deadline.Sub(time.Now())
+	if timeout < 0 {
+		timeout = 0
+	}
+	t := time.NewTimer(timeout)
+	defer t.Stop()
+	type pair struct {
+		Conn
+		error
+	}
+	ch := make(chan pair, 1)
+	resolvedAddr := make(chan Addr, 1)
+	go func() {
+		ra, err := resolveAddr("dial", net, addr, noDeadline)
+		if err != nil {
+			ch <- pair{nil, err}
+			return
+		}
+		resolvedAddr <- ra // in case we need it for OpError
+		c, err := dial(net, addr, localAddr, ra, noDeadline)
+		ch <- pair{c, err}
+	}()
+	select {
+	case <-t.C:
+		// Try to use the real Addr in our OpError, if we resolved it
+		// before the timeout. Otherwise we just use stringAddr.
+		var ra Addr
+		select {
+		case a := <-resolvedAddr:
+			ra = a
+		default:
+			ra = &stringAddr{net, addr}
+		}
+		err := &OpError{
+			Op:   "dial",
+			Net:  net,
+			Addr: ra,
+			Err:  &timeoutError{},
+		}
+		return nil, err
+	case p := <-ch:
+		return p.Conn, p.error
+	}
+}

src/pkg/net/fd_plan9.go, src/pkg/net/fd_unix.go, src/pkg/net/fd_windows.go

これらのファイルでは、プラットフォーム固有のダイヤル処理関数が変更されています。特に、dialTimeout関数がresolveAndDial関数に置き換えられ、新しいDialer構造体と連携するように修正されました。

--- a/src/pkg/net/fd_plan9.go
+++ b/src/pkg/net/fd_plan9.go
@@ -23,10 +23,10 @@ var canCancelIO = true // used for testing current package
 func sysInit() {
 }
 
-func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
+func resolveAndDial(net, addr string, localAddr Addr, deadline time.Time) (Conn, error) {
 	// On plan9, use the relatively inefficient
 	// goroutine-racing implementation.
-	return dialTimeoutRace(net, addr, timeout)
+	return resolveAndDialChannel(net, addr, localAddr, deadline)
 }
 
 func newFD(proto, name string, ctl, data *os.File, laddr, raddr Addr) *netFD {
--- a/src/pkg/net/fd_unix.go
+++ b/src/pkg/net/fd_unix.go
@@ -41,13 +41,12 @@ type netFD struct {
 	pd pollDesc
 }
 
-func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
-	deadline := time.Now().Add(timeout)
+func resolveAndDial(net, addr string, localAddr Addr, deadline time.Time) (Conn, error) {
 	ra, err := resolveAddr("dial", net, addr, deadline)
 	if err != nil {
 		return nil, err
 	}
-	return dial(net, addr, noLocalAddr, ra, deadline)
+	return dial(net, addr, localAddr, ra, deadline)
 }
 
 func newFD(fd, family, sotype int, net string) (*netFD, error) {
--- a/src/pkg/net/fd_windows.go
+++ b/src/pkg/net/fd_windows.go
@@ -54,18 +54,17 @@ func canUseConnectEx(net string) bool {
 	return syscall.LoadConnectEx() == nil
 }
 
-func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
+func resolveAndDial(net, addr string, localAddr Addr, deadline time.Time) (Conn, error) {
 	if !canUseConnectEx(net) {
 		// Use the relatively inefficient goroutine-racing
 		// implementation of DialTimeout.
-		return dialTimeoutRace(net, addr, timeout)
+		return resolveAndDialChannel(net, addr, localAddr, deadline)
 	}
-	deadline := time.Now().Add(timeout)
 	ra, err := resolveAddr("dial", net, addr, deadline)
 	if err != nil {
 		return nil, err
 	}
-	return dial(net, addr, noLocalAddr, ra, deadline)
+	return dial(net, addr, localAddr, ra, deadline)
 }
 
 // Interface for all IO operations.

コアとなるコードの解説

このコミットの核心は、Goのnetパッケージにおけるネットワークダイヤルのオプション指定方法を、よりGoらしい慣用的なパターンに統一した点にあります。

Dialer構造体によるオプションのカプセル化

以前のDialOptionインターフェースは、各オプションがsetDialOptメソッドを実装するという、やや回りくどい方法でオプションを適用していました。これは、オプションの追加や組み合わせの際に、新しい型やインターフェースの実装を増やす必要があり、柔軟性に欠けていました。

新しいDialer構造体は、TimeoutDeadlineLocalAddrといったダイヤルオプションを直接フィールドとして持ちます。これにより、ユーザーはDialer構造体のインスタンスを作成し、必要なフィールドを直接設定するだけで、ダイヤル動作をカスタマイズできるようになりました。

// 例: タイムアウトを設定してダイヤル
d := net.Dialer{Timeout: 5 * time.Second}
conn, err := d.Dial("tcp", "localhost:8080")

// 例: ローカルアドレスとデッドラインを設定してダイヤル
d2 := net.Dialer{
    LocalAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0},
    Deadline:  time.Now().Add(10 * time.Second),
}
conn2, err2 := d2.Dial("tcp", "example.com:80")

このアプローチは、Goの他の標準ライブラリ(例: http.Client)でも見られる一般的なパターンであり、一貫性と使いやすさが向上します。

Dialer.deadline()メソッドの役割

Dialer構造体に追加されたdeadline()メソッドは、TimeoutDeadlineの両方が設定されている場合に、より早い方の時刻を実際のデッドラインとして計算します。これにより、ユーザーはタイムアウトと絶対的なデッドラインの両方を柔軟に指定でき、システムはそれらを適切に処理できます。

既存のDial関数の内部変更

既存のnet.Dial関数とnet.DialTimeout関数が、内部的に新しいDialer構造体を使用するように変更されたことは重要です。これにより、このAPI変更が後方互換性を維持しつつ導入されました。ユーザーは既存のDial関数をそのまま使い続けることができ、必要に応じてDialer構造体を使ってより詳細なオプションを指定できるようになりました。

プラットフォーム固有のダイヤル処理の統一

fd_plan9.gofd_unix.gofd_windows.goといったファイルにおける変更は、各オペレーティングシステムでの実際のソケットダイヤル処理が、新しいDialer構造体から渡されるオプション(特にデッドラインとローカルアドレス)を適切に利用するように調整されたことを示しています。特に、dial_gen.goで導入されたresolveAndDialChannelは、一部のプラットフォームでより汎用的なタイムアウト処理を提供するためのフォールバックメカニズムとして機能します。

全体として、このコミットはGoのnetパッケージのAPIをよりクリーンで、柔軟で、Goらしい設計に進化させるための重要なステップでした。

関連リンク

参考にした情報源リンク