Package net provides a portable interface for network I/O, including
TCP/IP, UDP, domain name resolution, and Unix domain sockets.
Although the package provides access to low-level networking
primitives, most clients will need only the basic interface provided
by the Dial, Listen, and Accept functions and the associated
Conn and Listener interfaces. The crypto/tls package uses
the same interfaces and similar Dial and Listen functions.
The Dial function connects to a server:
conn, err := net.Dial("tcp", "golang.org:80") if err != nil { // handle error } fmt.Fprintf(conn, "GET / HTTP/1.0rnrn") status, err := bufio.NewReader(conn).ReadString('n') // ...
The Listen function creates servers:
ln, err := net.Listen("tcp", ":8080") if err != nil { // handle error } for { conn, err := ln.Accept() if err != nil { // handle error } go handleConnection(conn) }
Name Resolution ¶
The method for resolving domain names, whether indirectly with functions like Dial
or directly with functions like LookupHost and LookupAddr, varies by operating system.
On Unix systems, the resolver has two options for resolving names.
It can use a pure Go resolver that sends DNS requests directly to the servers
listed in /etc/resolv.conf, or it can use a cgo-based resolver that calls C
library routines such as getaddrinfo and getnameinfo.
By default the pure Go resolver is used, because a blocked DNS request consumes
only a goroutine, while a blocked C call consumes an operating system thread.
When cgo is available, the cgo-based resolver is used instead under a variety of
conditions: on systems that do not let programs make direct DNS requests (OS X),
when the LOCALDOMAIN environment variable is present (even if empty),
when the RES_OPTIONS or HOSTALIASES environment variable is non-empty,
when the ASR_CONFIG environment variable is non-empty (OpenBSD only),
when /etc/resolv.conf or /etc/nsswitch.conf specify the use of features that the
Go resolver does not implement, and when the name being looked up ends in .local
or is an mDNS name.
The resolver decision can be overridden by setting the netdns value of the
GODEBUG environment variable (see package runtime) to go or cgo, as in:
export GODEBUG=netdns=go # force pure Go resolver export GODEBUG=netdns=cgo # force native resolver (cgo, win32)
The decision can also be forced while building the Go source tree
by setting the netgo or netcgo build tag.
A numeric netdns setting, as in GODEBUG=netdns=1, causes the resolver
to print debugging information about its decisions.
To force a particular resolver while also printing debugging information,
join the two settings by a plus sign, as in GODEBUG=netdns=go+1.
On Plan 9, the resolver always accesses /net/cs and /net/dns.
On Windows, in Go 1.18.x and earlier, the resolver always used C
library functions, such as GetAddrInfo and DnsQuery.
- Constants
- Variables
- func JoinHostPort(host, port string) string
- func LookupAddr(addr string) (names []string, err error)
- func LookupCNAME(host string) (cname string, err error)
- func LookupHost(host string) (addrs []string, err error)
- func LookupPort(network, service string) (port int, err error)
- func LookupTXT(name string) ([]string, error)
- func ParseCIDR(s string) (IP, *IPNet, error)
- func Pipe() (Conn, Conn)
- func SplitHostPort(hostport string) (host, port string, err error)
- type Addr
-
- func InterfaceAddrs() ([]Addr, error)
- type AddrError
-
- func (e *AddrError) Error() string
- func (e *AddrError) Temporary() bool
- func (e *AddrError) Timeout() bool
- type Buffers
-
- func (v *Buffers) Read(p []byte) (n int, err error)
- func (v *Buffers) WriteTo(w io.Writer) (n int64, err error)
- type Conn
-
- func Dial(network, address string) (Conn, error)
- func DialTimeout(network, address string, timeout time.Duration) (Conn, error)
- func FileConn(f *os.File) (c Conn, err error)
- type DNSConfigError
-
- func (e *DNSConfigError) Error() string
- func (e *DNSConfigError) Temporary() bool
- func (e *DNSConfigError) Timeout() bool
- func (e *DNSConfigError) Unwrap() error
- type DNSError
-
- func (e *DNSError) Error() string
- func (e *DNSError) Temporary() bool
- func (e *DNSError) Timeout() bool
- type Dialer
-
- func (d *Dialer) Dial(network, address string) (Conn, error)
- func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error)
- type Error
- type Flags
-
- func (f Flags) String() string
- type HardwareAddr
-
- func ParseMAC(s string) (hw HardwareAddr, err error)
-
- func (a HardwareAddr) String() string
- type IP
-
- func IPv4(a, b, c, d byte) IP
- func LookupIP(host string) ([]IP, error)
- func ParseIP(s string) IP
-
- func (ip IP) DefaultMask() IPMask
- func (ip IP) Equal(x IP) bool
- func (ip IP) IsGlobalUnicast() bool
- func (ip IP) IsInterfaceLocalMulticast() bool
- func (ip IP) IsLinkLocalMulticast() bool
- func (ip IP) IsLinkLocalUnicast() bool
- func (ip IP) IsLoopback() bool
- func (ip IP) IsMulticast() bool
- func (ip IP) IsPrivate() bool
- func (ip IP) IsUnspecified() bool
- func (ip IP) MarshalText() ([]byte, error)
- func (ip IP) Mask(mask IPMask) IP
- func (ip IP) String() string
- func (ip IP) To16() IP
- func (ip IP) To4() IP
- func (ip *IP) UnmarshalText(text []byte) error
- type IPAddr
-
- func ResolveIPAddr(network, address string) (*IPAddr, error)
-
- func (a *IPAddr) Network() string
- func (a *IPAddr) String() string
- type IPConn
-
- func DialIP(network string, laddr, raddr *IPAddr) (*IPConn, error)
- func ListenIP(network string, laddr *IPAddr) (*IPConn, error)
-
- func (c *IPConn) Close() error
- func (c *IPConn) File() (f *os.File, err error)
- func (c *IPConn) LocalAddr() Addr
- func (c *IPConn) Read(b []byte) (int, error)
- func (c *IPConn) ReadFrom(b []byte) (int, Addr, error)
- func (c *IPConn) ReadFromIP(b []byte) (int, *IPAddr, error)
- func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err error)
- func (c *IPConn) RemoteAddr() Addr
- func (c *IPConn) SetDeadline(t time.Time) error
- func (c *IPConn) SetReadBuffer(bytes int) error
- func (c *IPConn) SetReadDeadline(t time.Time) error
- func (c *IPConn) SetWriteBuffer(bytes int) error
- func (c *IPConn) SetWriteDeadline(t time.Time) error
- func (c *IPConn) SyscallConn() (syscall.RawConn, error)
- func (c *IPConn) Write(b []byte) (int, error)
- func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error)
- func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error)
- func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error)
- type IPMask
-
- func CIDRMask(ones, bits int) IPMask
- func IPv4Mask(a, b, c, d byte) IPMask
-
- func (m IPMask) Size() (ones, bits int)
- func (m IPMask) String() string
- type IPNet
-
- func (n *IPNet) Contains(ip IP) bool
- func (n *IPNet) Network() string
- func (n *IPNet) String() string
- type Interface
-
- func InterfaceByIndex(index int) (*Interface, error)
- func InterfaceByName(name string) (*Interface, error)
- func Interfaces() ([]Interface, error)
-
- func (ifi *Interface) Addrs() ([]Addr, error)
- func (ifi *Interface) MulticastAddrs() ([]Addr, error)
- type InvalidAddrError
-
- func (e InvalidAddrError) Error() string
- func (e InvalidAddrError) Temporary() bool
- func (e InvalidAddrError) Timeout() bool
- type ListenConfig
-
- func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error)
- func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (PacketConn, error)
- type Listener
-
- func FileListener(f *os.File) (ln Listener, err error)
- func Listen(network, address string) (Listener, error)
- type MX
-
- func LookupMX(name string) ([]*MX, error)
- type NS
-
- func LookupNS(name string) ([]*NS, error)
- type OpError
-
- func (e *OpError) Error() string
- func (e *OpError) Temporary() bool
- func (e *OpError) Timeout() bool
- func (e *OpError) Unwrap() error
- type PacketConn
-
- func FilePacketConn(f *os.File) (c PacketConn, err error)
- func ListenPacket(network, address string) (PacketConn, error)
- type ParseError
-
- func (e *ParseError) Error() string
- func (e *ParseError) Temporary() bool
- func (e *ParseError) Timeout() bool
- type Resolver
-
- func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, error)
- func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error)
- func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error)
- func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]IP, error)
- func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error)
- func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error)
- func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error)
- func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
- func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error)
- func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error)
- func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error)
- type SRV
-
- func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error)
- type TCPAddr
-
- func ResolveTCPAddr(network, address string) (*TCPAddr, error)
- func TCPAddrFromAddrPort(addr netip.AddrPort) *TCPAddr
-
- func (a *TCPAddr) AddrPort() netip.AddrPort
- func (a *TCPAddr) Network() string
- func (a *TCPAddr) String() string
- type TCPConn
-
- func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
-
- func (c *TCPConn) Close() error
- func (c *TCPConn) CloseRead() error
- func (c *TCPConn) CloseWrite() error
- func (c *TCPConn) File() (f *os.File, err error)
- func (c *TCPConn) LocalAddr() Addr
- func (c *TCPConn) Read(b []byte) (int, error)
- func (c *TCPConn) ReadFrom(r io.Reader) (int64, error)
- func (c *TCPConn) RemoteAddr() Addr
- func (c *TCPConn) SetDeadline(t time.Time) error
- func (c *TCPConn) SetKeepAlive(keepalive bool) error
- func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error
- func (c *TCPConn) SetLinger(sec int) error
- func (c *TCPConn) SetNoDelay(noDelay bool) error
- func (c *TCPConn) SetReadBuffer(bytes int) error
- func (c *TCPConn) SetReadDeadline(t time.Time) error
- func (c *TCPConn) SetWriteBuffer(bytes int) error
- func (c *TCPConn) SetWriteDeadline(t time.Time) error
- func (c *TCPConn) SyscallConn() (syscall.RawConn, error)
- func (c *TCPConn) Write(b []byte) (int, error)
- type TCPListener
-
- func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
-
- func (l *TCPListener) Accept() (Conn, error)
- func (l *TCPListener) AcceptTCP() (*TCPConn, error)
- func (l *TCPListener) Addr() Addr
- func (l *TCPListener) Close() error
- func (l *TCPListener) File() (f *os.File, err error)
- func (l *TCPListener) SetDeadline(t time.Time) error
- func (l *TCPListener) SyscallConn() (syscall.RawConn, error)
- type UDPAddr
-
- func ResolveUDPAddr(network, address string) (*UDPAddr, error)
- func UDPAddrFromAddrPort(addr netip.AddrPort) *UDPAddr
-
- func (a *UDPAddr) AddrPort() netip.AddrPort
- func (a *UDPAddr) Network() string
- func (a *UDPAddr) String() string
- type UDPConn
-
- func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
- func ListenMulticastUDP(network string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error)
- func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error)
-
- func (c *UDPConn) Close() error
- func (c *UDPConn) File() (f *os.File, err error)
- func (c *UDPConn) LocalAddr() Addr
- func (c *UDPConn) Read(b []byte) (int, error)
- func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error)
- func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error)
- func (c *UDPConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error)
- func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error)
- func (c *UDPConn) ReadMsgUDPAddrPort(b, oob []byte) (n, oobn, flags int, addr netip.AddrPort, err error)
- func (c *UDPConn) RemoteAddr() Addr
- func (c *UDPConn) SetDeadline(t time.Time) error
- func (c *UDPConn) SetReadBuffer(bytes int) error
- func (c *UDPConn) SetReadDeadline(t time.Time) error
- func (c *UDPConn) SetWriteBuffer(bytes int) error
- func (c *UDPConn) SetWriteDeadline(t time.Time) error
- func (c *UDPConn) SyscallConn() (syscall.RawConn, error)
- func (c *UDPConn) Write(b []byte) (int, error)
- func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error)
- func (c *UDPConn) WriteMsgUDPAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error)
- func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error)
- func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)
- func (c *UDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error)
- type UnixAddr
-
- func ResolveUnixAddr(network, address string) (*UnixAddr, error)
-
- func (a *UnixAddr) Network() string
- func (a *UnixAddr) String() string
- type UnixConn
-
- func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)
- func ListenUnixgram(network string, laddr *UnixAddr) (*UnixConn, error)
-
- func (c *UnixConn) Close() error
- func (c *UnixConn) CloseRead() error
- func (c *UnixConn) CloseWrite() error
- func (c *UnixConn) File() (f *os.File, err error)
- func (c *UnixConn) LocalAddr() Addr
- func (c *UnixConn) Read(b []byte) (int, error)
- func (c *UnixConn) ReadFrom(b []byte) (int, Addr, error)
- func (c *UnixConn) ReadFromUnix(b []byte) (int, *UnixAddr, error)
- func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAddr, err error)
- func (c *UnixConn) RemoteAddr() Addr
- func (c *UnixConn) SetDeadline(t time.Time) error
- func (c *UnixConn) SetReadBuffer(bytes int) error
- func (c *UnixConn) SetReadDeadline(t time.Time) error
- func (c *UnixConn) SetWriteBuffer(bytes int) error
- func (c *UnixConn) SetWriteDeadline(t time.Time) error
- func (c *UnixConn) SyscallConn() (syscall.RawConn, error)
- func (c *UnixConn) Write(b []byte) (int, error)
- func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err error)
- func (c *UnixConn) WriteTo(b []byte, addr Addr) (int, error)
- func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (int, error)
- type UnixListener
-
- func ListenUnix(network string, laddr *UnixAddr) (*UnixListener, error)
-
- func (l *UnixListener) Accept() (Conn, error)
- func (l *UnixListener) AcceptUnix() (*UnixConn, error)
- func (l *UnixListener) Addr() Addr
- func (l *UnixListener) Close() error
- func (l *UnixListener) File() (f *os.File, err error)
- func (l *UnixListener) SetDeadline(t time.Time) error
- func (l *UnixListener) SetUnlinkOnClose(unlink bool)
- func (l *UnixListener) SyscallConn() (syscall.RawConn, error)
- type UnknownNetworkError
-
- func (e UnknownNetworkError) Error() string
- func (e UnknownNetworkError) Temporary() bool
- func (e UnknownNetworkError) Timeout() bool
- Bugs
- CIDRMask
- Dialer
- Dialer (Unix)
- IP (To4)
- IP.DefaultMask
- IP.Equal
- IP.IsGlobalUnicast
- IP.IsInterfaceLocalMulticast
- IP.IsLinkLocalMulticast
- IP.IsLinkLocalUnicast
- IP.IsLoopback
- IP.IsMulticast
- IP.IsPrivate
- IP.IsUnspecified
- IP.Mask
- IP.String
- IP.To16
- IPv4
- IPv4Mask
- Listener
- ParseCIDR
- ParseIP
- UDPConn.WriteTo
IP address lengths (bytes).
View Source
var ( IPv4bcast = IPv4(255, 255, 255, 255) IPv4allsys = IPv4(224, 0, 0, 1) IPv4allrouter = IPv4(224, 0, 0, 2) IPv4zero = IPv4(0, 0, 0, 0) )
Well-known IPv4 addresses
View Source
var ( IPv6zero = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} IPv6unspecified = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} IPv6loopback = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} IPv6interfacelocalallnodes = IP{0xff, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01} IPv6linklocalallnodes = IP{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01} IPv6linklocalallrouters = IP{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02} )
Well-known IPv6 addresses
DefaultResolver is the resolver used by the package-level Lookup
functions and by Dialers without a specified Resolver.
ErrClosed is the error returned by an I/O call on a network
connection that has already been closed, or that is closed by
another goroutine before the I/O is completed. This may be wrapped
in another error, and should normally be tested using
errors.Is(err, net.ErrClosed).
Various errors contained in OpError.
JoinHostPort combines host and port into a network address of the
form «host:port». If host contains a colon, as found in literal
IPv6 addresses, then JoinHostPort returns «[host]:port».
See func Dial for a description of the host and port parameters.
LookupAddr performs a reverse lookup for the given address, returning a list
of names mapping to that address.
The returned names are validated to be properly formatted presentation-format
domain names. If the response contains invalid names, those records are filtered
out and an error will be returned alongside the remaining results, if any.
When using the host C library resolver, at most one result will be
returned. To bypass the host resolver, use a custom Resolver.
LookupAddr uses context.Background internally; to specify the context, use
Resolver.LookupAddr.
LookupCNAME returns the canonical name for the given host.
Callers that do not care about the canonical name can call
LookupHost or LookupIP directly; both take care of resolving
the canonical name as part of the lookup.
A canonical name is the final name after following zero
or more CNAME records.
LookupCNAME does not return an error if host does not
contain DNS «CNAME» records, as long as host resolves to
address records.
The returned canonical name is validated to be a properly
formatted presentation-format domain name.
LookupCNAME uses context.Background internally; to specify the context, use
Resolver.LookupCNAME.
LookupHost looks up the given host using the local resolver.
It returns a slice of that host’s addresses.
LookupHost uses context.Background internally; to specify the context, use
Resolver.LookupHost.
LookupPort looks up the port for the given network and service.
LookupPort uses context.Background internally; to specify the context, use
Resolver.LookupPort.
LookupTXT returns the DNS TXT records for the given domain name.
LookupTXT uses context.Background internally; to specify the context, use
Resolver.LookupTXT.
ParseCIDR parses s as a CIDR notation IP address and prefix length,
like «192.0.2.0/24» or «2001:db8::/32», as defined in
RFC 4632 and RFC 4291.
It returns the IP address and the network implied by the IP and
prefix length.
For example, ParseCIDR(«192.0.2.1/24») returns the IP address
192.0.2.1 and the network 192.0.2.0/24.
package main import ( "fmt" "log" "net" ) func main() { ipv4Addr, ipv4Net, err := net.ParseCIDR("192.0.2.1/24") if err != nil { log.Fatal(err) } fmt.Println(ipv4Addr) fmt.Println(ipv4Net) ipv6Addr, ipv6Net, err := net.ParseCIDR("2001:db8:a0b:12f0::1/32") if err != nil { log.Fatal(err) } fmt.Println(ipv6Addr) fmt.Println(ipv6Net) }
Output: 192.0.2.1 192.0.2.0/24 2001:db8:a0b:12f0::1 2001:db8::/32
Pipe creates a synchronous, in-memory, full duplex
network connection; both ends implement the Conn interface.
Reads on one end are matched with writes on the other,
copying data directly between the two; there is no internal
buffering.
SplitHostPort splits a network address of the form «host:port»,
«host%zone:port», «[host]:port» or «[host%zone]:port» into host or
host%zone and port.
A literal IPv6 address in hostport must be enclosed in square
brackets, as in «[::1]:80», «[::1%lo0]:80».
See func Dial for a description of the hostport parameter, and host
and port results.
Addr represents a network end point address.
The two methods Network and String conventionally return strings
that can be passed as the arguments to Dial, but the exact form
and meaning of the strings is up to the implementation.
func InterfaceAddrs() ([]Addr, error)
InterfaceAddrs returns a list of the system’s unicast interface
addresses.
The returned list does not identify the associated interface; use
Interfaces and Interface.Addrs for more detail.
func (e *AddrError) Temporary() bool
func (e *AddrError) Timeout() bool
Buffers contains zero or more runs of bytes to write.
On certain machines, for certain types of connections, this is
optimized into an OS-specific batch write operation (such as
«writev»).
Read from the buffers.
Read implements io.Reader for Buffers.
Read modifies the slice v as well as v[i] for 0 <= i < len(v),
but does not modify v[i][j] for any i, j.
WriteTo writes contents of the buffers to w.
WriteTo implements io.WriterTo for Buffers.
WriteTo modifies the slice v as well as v[i] for 0 <= i < len(v),
but does not modify v[i][j] for any i, j.
Conn is a generic stream-oriented network connection.
Multiple goroutines may invoke methods on a Conn simultaneously.
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».
For TCP and UDP networks, the address has the form «host:port».
The host must be a literal IP address, or a host name that can be
resolved to IP addresses.
The port must be a literal port number or a service name.
If the host is a literal IPv6 address it must be enclosed in square
brackets, as in «[2001:db8::1]:80» or «[fe80::1%zone]:80».
The zone specifies the scope of the literal IPv6 address as defined
in RFC 4007.
The functions JoinHostPort and SplitHostPort manipulate a pair of
host and port in this form.
When using TCP, and the host resolves to multiple IP addresses,
Dial will try each IP address in order until one succeeds.
Examples:
Dial("tcp", "golang.org:http") Dial("tcp", "192.0.2.1:http") Dial("tcp", "198.51.100.1:80") Dial("udp", "[2001:db8::1]:domain") Dial("udp", "[fe80::1%lo0]:53") Dial("tcp", ":80")
For IP networks, the network must be «ip», «ip4» or «ip6» followed
by a colon and a literal protocol number or a protocol name, and
the address has the form «host». The host must be a literal IP
address or a literal IPv6 address with zone.
It depends on each operating system how the operating system
behaves with a non-well known protocol number such as «0» or «255».
Examples:
Dial("ip4:1", "192.0.2.1") Dial("ip6:ipv6-icmp", "2001:db8::1") Dial("ip6:58", "fe80::1%lo0")
For TCP, UDP and IP networks, if the host is empty or a literal
unspecified IP address, as in «:80», «0.0.0.0:80» or «[::]:80» for
TCP and UDP, «», «0.0.0.0» or «::» for IP, the local system is
assumed.
For Unix networks, the address must be a file system path.
DialTimeout acts like Dial but takes a timeout.
The timeout includes name resolution, if required.
When using TCP, and the host in the address parameter resolves to
multiple IP addresses, the timeout is spread over each consecutive
dial, such that each is given an appropriate fraction of the time
to connect.
See func Dial for a description of the network and address
parameters.
FileConn returns a copy of the network connection corresponding to
the open file f.
It is the caller’s responsibility to close f when finished.
Closing c does not affect f, and closing f does not affect c.
type DNSConfigError struct {
Err error
}
DNSConfigError represents an error reading the machine’s DNS configuration.
(No longer used; kept for compatibility.)
func (e *DNSConfigError) Temporary() bool
DNSError represents a DNS lookup error.
func (e *DNSError) Temporary() bool
Temporary reports whether the DNS error is known to be temporary.
This is not always known; a DNS lookup may fail due to a temporary
error and return a DNSError for which Temporary returns false.
func (e *DNSError) Timeout() bool
Timeout reports whether the DNS lookup is known to have timed out.
This is not always known; a DNS lookup may fail due to a timeout
and return a DNSError for which Timeout returns false.
A Dialer contains options for connecting to an address.
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.
It is safe to call Dialer’s methods concurrently.
package main import ( "context" "log" "net" "time" ) func main() { var d net.Dialer ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() conn, err := d.DialContext(ctx, "tcp", "localhost:12345") if err != nil { log.Fatalf("Failed to dial: %v", err) } defer conn.Close() if _, err := conn.Write([]byte("Hello, World!")); err != nil { log.Fatal(err) } }
Output:
package main import ( "context" "log" "net" "time" ) func main() { // DialUnix does not take a context.Context parameter. This example shows // how to dial a Unix socket with a Context. Note that the Context only // applies to the dial operation; it does not apply to the connection once // it has been established. var d net.Dialer ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() d.LocalAddr = nil // if you have a local addr, add it here raddr := net.UnixAddr{Name: "/path/to/unix.sock", Net: "unix"} conn, err := d.DialContext(ctx, "unix", raddr.String()) if err != nil { log.Fatalf("Failed to dial: %v", err) } defer conn.Close() if _, err := conn.Write([]byte("Hello, socket!")); err != nil { log.Fatal(err) } }
Output:
Dial connects to the address on the named network.
See func Dial for a description of the network and address
parameters.
Dial uses context.Background internally; to specify the context, use
DialContext.
DialContext connects to the address on the named network using
the provided context.
The provided Context must be non-nil. If the context expires before
the connection is complete, an error is returned. Once successfully
connected, any expiration of the context will not affect the
connection.
When using TCP, and the host in the address parameter resolves to multiple
network addresses, any dial timeout (from d.Timeout or ctx) is spread
over each consecutive dial, such that each is given an appropriate
fraction of the time to connect.
For example, if a host has 4 IP addresses and the timeout is 1 minute,
the connect to each single address will be given 15 seconds to complete
before trying the next one.
See func Dial for a description of the network and address
parameters.
An Error represents a network error.
const ( FlagUp Flags = 1 << iota FlagBroadcast FlagLoopback FlagPointToPoint FlagMulticast FlagRunning )
A HardwareAddr represents a physical hardware address.
ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet
IP over InfiniBand link-layer address using one of the following formats:
00:00:5e:00:53:01 02:00:5e:10:00:00:00:01 00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01 00-00-5e-00-53-01 02-00-5e-10-00-00-00-01 00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01 0000.5e00.5301 0200.5e10.0000.0001 0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001
An IP is a single IP address, a slice of bytes.
Functions in this package accept either 4-byte (IPv4)
or 16-byte (IPv6) slices as input.
Note that in this documentation, referring to an
IP address as an IPv4 address or an IPv6 address
is a semantic property of the address, not just the
length of the byte slice: a 16-byte slice can still
be an IPv4 address.
package main import ( "fmt" "net" ) func main() { ipv6 := net.IP{0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ipv4 := net.IPv4(10, 255, 0, 0) fmt.Println(ipv6.To4()) fmt.Println(ipv4.To4()) }
Output: <nil> 10.255.0.0
func IPv4(a, b, c, d byte) IP
IPv4 returns the IP address (in 16-byte form) of the
IPv4 address a.b.c.d.
package main import ( "fmt" "net" ) func main() { fmt.Println(net.IPv4(8, 8, 8, 8)) }
Output: 8.8.8.8
LookupIP looks up host using the local resolver.
It returns a slice of that host’s IPv4 and IPv6 addresses.
ParseIP parses s as an IP address, returning the result.
The string s can be in IPv4 dotted decimal («192.0.2.1»), IPv6
(«2001:db8::68»), or IPv4-mapped IPv6 («::ffff:192.0.2.1») form.
If s is not a valid textual representation of an IP address,
ParseIP returns nil.
package main import ( "fmt" "net" ) func main() { fmt.Println(net.ParseIP("192.0.2.1")) fmt.Println(net.ParseIP("2001:db8::68")) fmt.Println(net.ParseIP("192.0.2")) }
Output: 192.0.2.1 2001:db8::68 <nil>
func (ip IP) DefaultMask() IPMask
DefaultMask returns the default IP mask for the IP address ip.
Only IPv4 addresses have default masks; DefaultMask returns
nil if ip is not a valid IPv4 address.
package main import ( "fmt" "net" ) func main() { ip := net.ParseIP("192.0.2.1") fmt.Println(ip.DefaultMask()) }
Output: ffffff00
func (ip IP) Equal(x IP) bool
Equal reports whether ip and x are the same IP address.
An IPv4 address and that same address in IPv6 form are
considered to be equal.
package main import ( "fmt" "net" ) func main() { ipv4DNS := net.ParseIP("8.8.8.8") ipv4Lo := net.ParseIP("127.0.0.1") ipv6DNS := net.ParseIP("0:0:0:0:0:FFFF:0808:0808") fmt.Println(ipv4DNS.Equal(ipv4DNS)) fmt.Println(ipv4DNS.Equal(ipv4Lo)) fmt.Println(ipv4DNS.Equal(ipv6DNS)) }
Output: true false true
func (ip IP) IsGlobalUnicast() bool
IsGlobalUnicast reports whether ip is a global unicast
address.
The identification of global unicast addresses uses address type
identification as defined in RFC 1122, RFC 4632 and RFC 4291 with
the exception of IPv4 directed broadcast addresses.
It returns true even if ip is in IPv4 private address space or
local IPv6 unicast address space.
package main import ( "fmt" "net" ) func main() { ipv6Global := net.ParseIP("2000::") ipv6UniqLocal := net.ParseIP("2000::") ipv6Multi := net.ParseIP("FF00::") ipv4Private := net.ParseIP("10.255.0.0") ipv4Public := net.ParseIP("8.8.8.8") ipv4Broadcast := net.ParseIP("255.255.255.255") fmt.Println(ipv6Global.IsGlobalUnicast()) fmt.Println(ipv6UniqLocal.IsGlobalUnicast()) fmt.Println(ipv6Multi.IsGlobalUnicast()) fmt.Println(ipv4Private.IsGlobalUnicast()) fmt.Println(ipv4Public.IsGlobalUnicast()) fmt.Println(ipv4Broadcast.IsGlobalUnicast()) }
Output: true true false true true false
func (ip IP) IsInterfaceLocalMulticast() bool
IsInterfaceLocalMulticast reports whether ip is
an interface-local multicast address.
package main import ( "fmt" "net" ) func main() { ipv6InterfaceLocalMulti := net.ParseIP("ff01::1") ipv6Global := net.ParseIP("2000::") ipv4 := net.ParseIP("255.0.0.0") fmt.Println(ipv6InterfaceLocalMulti.IsInterfaceLocalMulticast()) fmt.Println(ipv6Global.IsInterfaceLocalMulticast()) fmt.Println(ipv4.IsInterfaceLocalMulticast()) }
Output: true false false
func (ip IP) IsLinkLocalMulticast() bool
IsLinkLocalMulticast reports whether ip is a link-local
multicast address.
package main import ( "fmt" "net" ) func main() { ipv6LinkLocalMulti := net.ParseIP("ff02::2") ipv6LinkLocalUni := net.ParseIP("fe80::") ipv4LinkLocalMulti := net.ParseIP("224.0.0.0") ipv4LinkLocalUni := net.ParseIP("169.254.0.0") fmt.Println(ipv6LinkLocalMulti.IsLinkLocalMulticast()) fmt.Println(ipv6LinkLocalUni.IsLinkLocalMulticast()) fmt.Println(ipv4LinkLocalMulti.IsLinkLocalMulticast()) fmt.Println(ipv4LinkLocalUni.IsLinkLocalMulticast()) }
Output: true false true false
func (ip IP) IsLinkLocalUnicast() bool
IsLinkLocalUnicast reports whether ip is a link-local
unicast address.
package main import ( "fmt" "net" ) func main() { ipv6LinkLocalUni := net.ParseIP("fe80::") ipv6Global := net.ParseIP("2000::") ipv4LinkLocalUni := net.ParseIP("169.254.0.0") ipv4LinkLocalMulti := net.ParseIP("224.0.0.0") fmt.Println(ipv6LinkLocalUni.IsLinkLocalUnicast()) fmt.Println(ipv6Global.IsLinkLocalUnicast()) fmt.Println(ipv4LinkLocalUni.IsLinkLocalUnicast()) fmt.Println(ipv4LinkLocalMulti.IsLinkLocalUnicast()) }
Output: true false true false
func (ip IP) IsLoopback() bool
IsLoopback reports whether ip is a loopback address.
package main import ( "fmt" "net" ) func main() { ipv6Lo := net.ParseIP("::1") ipv6 := net.ParseIP("ff02::1") ipv4Lo := net.ParseIP("127.0.0.0") ipv4 := net.ParseIP("128.0.0.0") fmt.Println(ipv6Lo.IsLoopback()) fmt.Println(ipv6.IsLoopback()) fmt.Println(ipv4Lo.IsLoopback()) fmt.Println(ipv4.IsLoopback()) }
Output: true false true false
func (ip IP) IsMulticast() bool
IsMulticast reports whether ip is a multicast address.
package main import ( "fmt" "net" ) func main() { ipv6Multi := net.ParseIP("FF00::") ipv6LinkLocalMulti := net.ParseIP("ff02::1") ipv6Lo := net.ParseIP("::1") ipv4Multi := net.ParseIP("239.0.0.0") ipv4LinkLocalMulti := net.ParseIP("224.0.0.0") ipv4Lo := net.ParseIP("127.0.0.0") fmt.Println(ipv6Multi.IsMulticast()) fmt.Println(ipv6LinkLocalMulti.IsMulticast()) fmt.Println(ipv6Lo.IsMulticast()) fmt.Println(ipv4Multi.IsMulticast()) fmt.Println(ipv4LinkLocalMulti.IsMulticast()) fmt.Println(ipv4Lo.IsMulticast()) }
Output: true true false true true false
func (ip IP) IsPrivate() bool
IsPrivate reports whether ip is a private address, according to
RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
package main import ( "fmt" "net" ) func main() { ipv6Private := net.ParseIP("fc00::") ipv6Public := net.ParseIP("fe00::") ipv4Private := net.ParseIP("10.255.0.0") ipv4Public := net.ParseIP("11.0.0.0") fmt.Println(ipv6Private.IsPrivate()) fmt.Println(ipv6Public.IsPrivate()) fmt.Println(ipv4Private.IsPrivate()) fmt.Println(ipv4Public.IsPrivate()) }
Output: true false true false
func (ip IP) IsUnspecified() bool
IsUnspecified reports whether ip is an unspecified address, either
the IPv4 address «0.0.0.0» or the IPv6 address «::».
package main import ( "fmt" "net" ) func main() { ipv6Unspecified := net.ParseIP("::") ipv6Specified := net.ParseIP("fe00::") ipv4Unspecified := net.ParseIP("0.0.0.0") ipv4Specified := net.ParseIP("8.8.8.8") fmt.Println(ipv6Unspecified.IsUnspecified()) fmt.Println(ipv6Specified.IsUnspecified()) fmt.Println(ipv4Unspecified.IsUnspecified()) fmt.Println(ipv4Specified.IsUnspecified()) }
Output: true false true false
MarshalText implements the encoding.TextMarshaler interface.
The encoding is the same as returned by String, with one exception:
When len(ip) is zero, it returns an empty slice.
func (ip IP) Mask(mask IPMask) IP
Mask returns the result of masking the IP address ip with mask.
package main import ( "fmt" "net" ) func main() { ipv4Addr := net.ParseIP("192.0.2.1") // This mask corresponds to a /24 subnet for IPv4. ipv4Mask := net.CIDRMask(24, 32) fmt.Println(ipv4Addr.Mask(ipv4Mask)) ipv6Addr := net.ParseIP("2001:db8:a0b:12f0::1") // This mask corresponds to a /32 subnet for IPv6. ipv6Mask := net.CIDRMask(32, 128) fmt.Println(ipv6Addr.Mask(ipv6Mask)) }
Output: 192.0.2.0 2001:db8::
String returns the string form of the IP address ip.
It returns one of 4 forms:
- «<nil>», if ip has length 0
- dotted decimal («192.0.2.1»), if ip is an IPv4 or IP4-mapped IPv6 address
- IPv6 conforming to RFC 5952 («2001:db8::1»), if ip is a valid IPv6 address
- the hexadecimal form of ip, without punctuation, if no other cases apply
package main import ( "fmt" "net" ) func main() { ipv6 := net.IP{0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ipv4 := net.IPv4(10, 255, 0, 0) fmt.Println(ipv6.String()) fmt.Println(ipv4.String()) }
Output: fc00:: 10.255.0.0
To16 converts the IP address ip to a 16-byte representation.
If ip is not an IP address (it is the wrong length), To16 returns nil.
package main import ( "fmt" "net" ) func main() { ipv6 := net.IP{0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ipv4 := net.IPv4(10, 255, 0, 0) fmt.Println(ipv6.To16()) fmt.Println(ipv4.To16()) }
Output: fc00:: 10.255.0.0
To4 converts the IPv4 address ip to a 4-byte representation.
If ip is not an IPv4 address, To4 returns nil.
UnmarshalText implements the encoding.TextUnmarshaler interface.
The IP address is expected in a form accepted by ParseIP.
type IPAddr struct { IP IP Zone string }
IPAddr represents the address of an IP end point.
ResolveIPAddr returns an address of IP end point.
The network must be an IP network name.
If the host in the address parameter is not a literal IP address,
ResolveIPAddr resolves the address to an address of IP end point.
Otherwise, it parses the address as a literal IP address.
The address parameter can use a host name, but this is not
recommended, because it will return at most one of the host name’s
IP addresses.
See func Dial for a description of the network and address
parameters.
Network returns the address’s network name, «ip».
IPConn is the implementation of the Conn and PacketConn interfaces
for IP network connections.
DialIP acts like Dial for IP networks.
The network must be an IP network name; see func Dial for details.
If laddr is nil, a local address is automatically chosen.
If the IP field of raddr is nil or an unspecified IP address, the
local system is assumed.
ListenIP acts like ListenPacket for IP networks.
The network must be an IP network name; see func Dial for details.
If the IP field of laddr is nil or an unspecified IP address,
ListenIP listens on all available IP addresses of the local system
except multicast IP addresses.
func (c *IPConn) Close() error
Close closes the connection.
File returns a copy of the underlying os.File.
It is the caller’s responsibility to close f when finished.
Closing c does not affect f, and closing f does not affect c.
The returned os.File’s file descriptor is different from the connection’s.
Attempting to change properties of the original using this duplicate
may or may not have the desired effect.
func (c *IPConn) LocalAddr() Addr
LocalAddr returns the local network address.
The Addr returned is shared by all invocations of LocalAddr, so
do not modify it.
Read implements the Conn Read method.
ReadFrom implements the PacketConn ReadFrom method.
ReadFromIP acts like ReadFrom but returns an IPAddr.
func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err error)
ReadMsgIP reads a message from c, copying the payload into b and
the associated out-of-band data into oob. It returns the number of
bytes copied into b, the number of bytes copied into oob, the flags
that were set on the message and the source address of the message.
The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
used to manipulate IP-level socket options in oob.
func (c *IPConn) RemoteAddr() Addr
RemoteAddr returns the remote network address.
The Addr returned is shared by all invocations of RemoteAddr, so
do not modify it.
SetDeadline implements the Conn SetDeadline method.
func (c *IPConn) SetReadBuffer(bytes int) error
SetReadBuffer sets the size of the operating system’s
receive buffer associated with the connection.
SetReadDeadline implements the Conn SetReadDeadline method.
func (c *IPConn) SetWriteBuffer(bytes int) error
SetWriteBuffer sets the size of the operating system’s
transmit buffer associated with the connection.
SetWriteDeadline implements the Conn SetWriteDeadline method.
SyscallConn returns a raw network connection.
This implements the syscall.Conn interface.
Write implements the Conn Write method.
func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error)
WriteMsgIP writes a message to addr via c, copying the payload from
b and the associated out-of-band data from oob. It returns the
number of payload and out-of-band bytes written.
The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
used to manipulate IP-level socket options in oob.
WriteTo implements the PacketConn WriteTo method.
WriteToIP acts like WriteTo but takes an IPAddr.
An IPMask is a bitmask that can be used to manipulate
IP addresses for IP addressing and routing.
See type IPNet and func ParseCIDR for details.
func CIDRMask(ones, bits int) IPMask
CIDRMask returns an IPMask consisting of ‘ones’ 1 bits
followed by 0s up to a total length of ‘bits’ bits.
For a mask of this form, CIDRMask is the inverse of IPMask.Size.
package main import ( "fmt" "net" ) func main() { // This mask corresponds to a /31 subnet for IPv4. fmt.Println(net.CIDRMask(31, 32)) // This mask corresponds to a /64 subnet for IPv6. fmt.Println(net.CIDRMask(64, 128)) }
Output: fffffffe ffffffffffffffff0000000000000000
func IPv4Mask(a, b, c, d byte) IPMask
IPv4Mask returns the IP mask (in 4-byte form) of the
IPv4 mask a.b.c.d.
package main import ( "fmt" "net" ) func main() { fmt.Println(net.IPv4Mask(255, 255, 255, 0)) }
Output: ffffff00
func (m IPMask) Size() (ones, bits int)
Size returns the number of leading ones and total bits in the mask.
If the mask is not in the canonical form—ones followed by zeros—then
Size returns 0, 0.
String returns the hexadecimal form of m, with no punctuation.
type IPNet struct { IP IP Mask IPMask }
An IPNet represents an IP network.
func (n *IPNet) Contains(ip IP) bool
Contains reports whether the network includes ip.
Network returns the address’s network name, «ip+net».
String returns the CIDR notation of n like «192.0.2.0/24»
or «2001:db8::/48» as defined in RFC 4632 and RFC 4291.
If the mask is not in the canonical form, it returns the
string which consists of an IP address, followed by a slash
character and a mask expressed as hexadecimal form with no
punctuation like «198.51.100.0/c000ff00».
type Interface struct { Index int MTU int Name string HardwareAddr HardwareAddr Flags Flags }
Interface represents a mapping between network interface name
and index. It also represents network interface facility
information.
InterfaceByIndex returns the interface specified by index.
On Solaris, it returns one of the logical network interfaces
sharing the logical data link; for more precision use
InterfaceByName.
InterfaceByName returns the interface specified by name.
Interfaces returns a list of the system’s network interfaces.
func (ifi *Interface) Addrs() ([]Addr, error)
Addrs returns a list of unicast interface addresses for a specific
interface.
func (ifi *Interface) MulticastAddrs() ([]Addr, error)
MulticastAddrs returns a list of multicast, joined group addresses
for a specific interface.
ListenConfig contains options for listening to an address.
Listen announces on the local network address.
See func Listen for a description of the network and address
parameters.
ListenPacket announces on the local network address.
See func ListenPacket for a description of the network and address
parameters.
type Listener interface { Accept() (Conn, error) Close() error Addr() Addr }
A Listener is a generic network listener for stream-oriented protocols.
Multiple goroutines may invoke methods on a Listener simultaneously.
package main import ( "io" "log" "net" ) func main() { // Listen on TCP port 2000 on all available unicast and // anycast IP addresses of the local system. l, err := net.Listen("tcp", ":2000") if err != nil { log.Fatal(err) } defer l.Close() for { // Wait for a connection. conn, err := l.Accept() if err != nil { log.Fatal(err) } // Handle the connection in a new goroutine. // The loop then returns to accepting, so that // multiple connections may be served concurrently. go func(c net.Conn) { // Echo all incoming data. io.Copy(c, c) // Shut down the connection. c.Close() }(conn) } }
Output:
FileListener returns a copy of the network listener corresponding
to the open file f.
It is the caller’s responsibility to close ln when finished.
Closing ln does not affect f, and closing f does not affect ln.
Listen announces on the local network address.
The network must be «tcp», «tcp4», «tcp6», «unix» or «unixpacket».
For TCP networks, if the host in the address parameter is empty or
a literal unspecified IP address, Listen listens on all available
unicast and anycast IP addresses of the local system.
To only use IPv4, use network «tcp4».
The address can use a host name, but this is not recommended,
because it will create a listener for at most one of the host’s IP
addresses.
If the port in the address parameter is empty or «0», as in
«127.0.0.1:» or «[::1]:0», a port number is automatically chosen.
The Addr method of Listener can be used to discover the chosen
port.
See func Dial for a description of the network and address
parameters.
Listen uses context.Background internally; to specify the context, use
ListenConfig.Listen.
An MX represents a single DNS MX record.
LookupMX returns the DNS MX records for the given domain name sorted by preference.
The returned mail server names are validated to be properly
formatted presentation-format domain names. If the response contains
invalid names, those records are filtered out and an error
will be returned alongside the remaining results, if any.
LookupMX uses context.Background internally; to specify the context, use
Resolver.LookupMX.
type NS struct {
Host string
}
An NS represents a single DNS NS record.
LookupNS returns the DNS NS records for the given domain name.
The returned name server names are validated to be properly
formatted presentation-format domain names. If the response contains
invalid names, those records are filtered out and an error
will be returned alongside the remaining results, if any.
LookupNS uses context.Background internally; to specify the context, use
Resolver.LookupNS.
OpError is the error type usually returned by functions in the net
package. It describes the operation, network type, and address of
an error.
func (e *OpError) Temporary() bool
func (e *OpError) Timeout() bool
PacketConn is a generic packet-oriented network connection.
Multiple goroutines may invoke methods on a PacketConn simultaneously.
FilePacketConn returns a copy of the packet network connection
corresponding to the open file f.
It is the caller’s responsibility to close f when finished.
Closing c does not affect f, and closing f does not affect c.
ListenPacket announces on the local network address.
The network must be «udp», «udp4», «udp6», «unixgram», or an IP
transport. The IP transports are «ip», «ip4», or «ip6» followed by
a colon and a literal protocol number or a protocol name, as in
«ip:1» or «ip:icmp».
For UDP and IP networks, if the host in the address parameter is
empty or a literal unspecified IP address, ListenPacket listens on
all available IP addresses of the local system except multicast IP
addresses.
To only use IPv4, use network «udp4» or «ip4:proto».
The address can use a host name, but this is not recommended,
because it will create a listener for at most one of the host’s IP
addresses.
If the port in the address parameter is empty or «0», as in
«127.0.0.1:» or «[::1]:0», a port number is automatically chosen.
The LocalAddr method of PacketConn can be used to discover the
chosen port.
See func Dial for a description of the network and address
parameters.
ListenPacket uses context.Background internally; to specify the context, use
ListenConfig.ListenPacket.
A ParseError is the error type of literal network address parsers.
func (e *ParseError) Temporary() bool
func (e *ParseError) Timeout() bool
A Resolver looks up names and numbers.
A nil *Resolver is equivalent to a zero Resolver.
LookupAddr performs a reverse lookup for the given address, returning a list
of names mapping to that address.
The returned names are validated to be properly formatted presentation-format
domain names. If the response contains invalid names, those records are filtered
out and an error will be returned alongside the remaining results, if any.
LookupCNAME returns the canonical name for the given host.
Callers that do not care about the canonical name can call
LookupHost or LookupIP directly; both take care of resolving
the canonical name as part of the lookup.
A canonical name is the final name after following zero
or more CNAME records.
LookupCNAME does not return an error if host does not
contain DNS «CNAME» records, as long as host resolves to
address records.
The returned canonical name is validated to be a properly
formatted presentation-format domain name.
LookupHost looks up the given host using the local resolver.
It returns a slice of that host’s addresses.
LookupIP looks up host for the given network using the local resolver.
It returns a slice of that host’s IP addresses of the type specified by
network.
network must be one of «ip», «ip4» or «ip6».
LookupIPAddr looks up host using the local resolver.
It returns a slice of that host’s IPv4 and IPv6 addresses.
LookupMX returns the DNS MX records for the given domain name sorted by preference.
The returned mail server names are validated to be properly
formatted presentation-format domain names. If the response contains
invalid names, those records are filtered out and an error
will be returned alongside the remaining results, if any.
LookupNS returns the DNS NS records for the given domain name.
The returned name server names are validated to be properly
formatted presentation-format domain names. If the response contains
invalid names, those records are filtered out and an error
will be returned alongside the remaining results, if any.
LookupNetIP looks up host using the local resolver.
It returns a slice of that host’s IP addresses of the type specified by
network.
The network must be one of «ip», «ip4» or «ip6».
LookupPort looks up the port for the given network and service.
LookupSRV tries to resolve an SRV query of the given service,
protocol, and domain name. The proto is «tcp» or «udp».
The returned records are sorted by priority and randomized
by weight within a priority.
LookupSRV constructs the DNS name to look up following RFC 2782.
That is, it looks up _service._proto.name. To accommodate services
publishing SRV records under non-standard names, if both service
and proto are empty strings, LookupSRV looks up name directly.
The returned service names are validated to be properly
formatted presentation-format domain names. If the response contains
invalid names, those records are filtered out and an error
will be returned alongside the remaining results, if any.
LookupTXT returns the DNS TXT records for the given domain name.
An SRV represents a single DNS SRV record.
LookupSRV tries to resolve an SRV query of the given service,
protocol, and domain name. The proto is «tcp» or «udp».
The returned records are sorted by priority and randomized
by weight within a priority.
LookupSRV constructs the DNS name to look up following RFC 2782.
That is, it looks up _service._proto.name. To accommodate services
publishing SRV records under non-standard names, if both service
and proto are empty strings, LookupSRV looks up name directly.
The returned service names are validated to be properly
formatted presentation-format domain names. If the response contains
invalid names, those records are filtered out and an error
will be returned alongside the remaining results, if any.
type TCPAddr struct { IP IP Port int Zone string }
TCPAddr represents the address of a TCP end point.
ResolveTCPAddr returns an address of TCP end point.
The network must be a TCP network name.
If the host in the address parameter is not a literal IP address or
the port is not a literal port number, ResolveTCPAddr resolves the
address to an address of TCP end point.
Otherwise, it parses the address as a pair of literal IP address
and port number.
The address parameter can use a host name, but this is not
recommended, because it will return at most one of the host name’s
IP addresses.
See func Dial for a description of the network and address
parameters.
TCPAddrFromAddrPort returns addr as a TCPAddr. If addr.IsValid() is false,
then the returned TCPAddr will contain a nil IP field, indicating an
address family-agnostic unspecified address.
AddrPort returns the TCPAddr a as a netip.AddrPort.
If a.Port does not fit in a uint16, it’s silently truncated.
If a is nil, a zero value is returned.
Network returns the address’s network name, «tcp».
TCPConn is an implementation of the Conn interface for TCP network
connections.
DialTCP acts like Dial for TCP networks.
The network must be a TCP network name; see func Dial for details.
If laddr is nil, a local address is automatically chosen.
If the IP field of raddr is nil or an unspecified IP address, the
local system is assumed.
func (c *TCPConn) Close() error
Close closes the connection.
CloseRead shuts down the reading side of the TCP connection.
Most callers should just use Close.
func (c *TCPConn) CloseWrite() error
CloseWrite shuts down the writing side of the TCP connection.
Most callers should just use Close.
File returns a copy of the underlying os.File.
It is the caller’s responsibility to close f when finished.
Closing c does not affect f, and closing f does not affect c.
The returned os.File’s file descriptor is different from the connection’s.
Attempting to change properties of the original using this duplicate
may or may not have the desired effect.
func (c *TCPConn) LocalAddr() Addr
LocalAddr returns the local network address.
The Addr returned is shared by all invocations of LocalAddr, so
do not modify it.
Read implements the Conn Read method.
ReadFrom implements the io.ReaderFrom ReadFrom method.
func (c *TCPConn) RemoteAddr() Addr
RemoteAddr returns the remote network address.
The Addr returned is shared by all invocations of RemoteAddr, so
do not modify it.
SetDeadline implements the Conn SetDeadline method.
SetKeepAlive sets whether the operating system should send
keep-alive messages on the connection.
SetKeepAlivePeriod sets period between keep-alives.
SetLinger sets the behavior of Close on a connection which still
has data waiting to be sent or to be acknowledged.
If sec < 0 (the default), the operating system finishes sending the
data in the background.
If sec == 0, the operating system discards any unsent or
unacknowledged data.
If sec > 0, the data is sent in the background as with sec < 0. On
some operating systems after sec seconds have elapsed any remaining
unsent data may be discarded.
SetNoDelay controls whether the operating system should delay
packet transmission in hopes of sending fewer packets (Nagle’s
algorithm). The default is true (no delay), meaning that data is
sent as soon as possible after a Write.
func (c *TCPConn) SetReadBuffer(bytes int) error
SetReadBuffer sets the size of the operating system’s
receive buffer associated with the connection.
SetReadDeadline implements the Conn SetReadDeadline method.
func (c *TCPConn) SetWriteBuffer(bytes int) error
SetWriteBuffer sets the size of the operating system’s
transmit buffer associated with the connection.
SetWriteDeadline implements the Conn SetWriteDeadline method.
SyscallConn returns a raw network connection.
This implements the syscall.Conn interface.
Write implements the Conn Write method.
type TCPListener struct { }
TCPListener is a TCP network listener. Clients should typically
use variables of type Listener instead of assuming TCP.
ListenTCP acts like Listen for TCP networks.
The network must be a TCP network name; see func Dial for details.
If the IP field of laddr is nil or an unspecified IP address,
ListenTCP listens on all available unicast and anycast IP addresses
of the local system.
If the Port field of laddr is 0, a port number is automatically
chosen.
Accept implements the Accept method in the Listener interface; it
waits for the next call and returns a generic Conn.
AcceptTCP accepts the next incoming call and returns the new
connection.
func (l *TCPListener) Addr() Addr
Addr returns the listener’s network address, a *TCPAddr.
The Addr returned is shared by all invocations of Addr, so
do not modify it.
Close stops listening on the TCP address.
Already Accepted connections are not closed.
File returns a copy of the underlying os.File.
It is the caller’s responsibility to close f when finished.
Closing l does not affect f, and closing f does not affect l.
The returned os.File’s file descriptor is different from the
connection’s. Attempting to change properties of the original
using this duplicate may or may not have the desired effect.
SetDeadline sets the deadline associated with the listener.
A zero time value disables the deadline.
SyscallConn returns a raw network connection.
This implements the syscall.Conn interface.
The returned RawConn only supports calling Control. Read and
Write return an error.
type UDPAddr struct { IP IP Port int Zone string }
UDPAddr represents the address of a UDP end point.
ResolveUDPAddr returns an address of UDP end point.
The network must be a UDP network name.
If the host in the address parameter is not a literal IP address or
the port is not a literal port number, ResolveUDPAddr resolves the
address to an address of UDP end point.
Otherwise, it parses the address as a pair of literal IP address
and port number.
The address parameter can use a host name, but this is not
recommended, because it will return at most one of the host name’s
IP addresses.
See func Dial for a description of the network and address
parameters.
UDPAddrFromAddrPort returns addr as a UDPAddr. If addr.IsValid() is false,
then the returned UDPAddr will contain a nil IP field, indicating an
address family-agnostic unspecified address.
AddrPort returns the UDPAddr a as a netip.AddrPort.
If a.Port does not fit in a uint16, it’s silently truncated.
If a is nil, a zero value is returned.
Network returns the address’s network name, «udp».
UDPConn is the implementation of the Conn and PacketConn interfaces
for UDP network connections.
DialUDP acts like Dial for UDP networks.
The network must be a UDP network name; see func Dial for details.
If laddr is nil, a local address is automatically chosen.
If the IP field of raddr is nil or an unspecified IP address, the
local system is assumed.
ListenMulticastUDP acts like ListenPacket for UDP networks but
takes a group address on a specific network interface.
The network must be a UDP network name; see func Dial for details.
ListenMulticastUDP listens on all available IP addresses of the
local system including the group, multicast IP address.
If ifi is nil, ListenMulticastUDP uses the system-assigned
multicast interface, although this is not recommended because the
assignment depends on platforms and sometimes it might require
routing configuration.
If the Port field of gaddr is 0, a port number is automatically
chosen.
ListenMulticastUDP is just for convenience of simple, small
applications. There are golang.org/x/net/ipv4 and
golang.org/x/net/ipv6 packages for general purpose uses.
Note that ListenMulticastUDP will set the IP_MULTICAST_LOOP socket option
to 0 under IPPROTO_IP, to disable loopback of multicast packets.
ListenUDP acts like ListenPacket for UDP networks.
The network must be a UDP network name; see func Dial for details.
If the IP field of laddr is nil or an unspecified IP address,
ListenUDP listens on all available IP addresses of the local system
except multicast IP addresses.
If the Port field of laddr is 0, a port number is automatically
chosen.
func (c *UDPConn) Close() error
Close closes the connection.
File returns a copy of the underlying os.File.
It is the caller’s responsibility to close f when finished.
Closing c does not affect f, and closing f does not affect c.
The returned os.File’s file descriptor is different from the connection’s.
Attempting to change properties of the original using this duplicate
may or may not have the desired effect.
func (c *UDPConn) LocalAddr() Addr
LocalAddr returns the local network address.
The Addr returned is shared by all invocations of LocalAddr, so
do not modify it.
Read implements the Conn Read method.
ReadFrom implements the PacketConn ReadFrom method.
ReadFromUDP acts like ReadFrom but returns a UDPAddr.
ReadFromUDPAddrPort acts like ReadFrom but returns a netip.AddrPort.
If c is bound to an unspecified address, the returned
netip.AddrPort’s address might be an IPv4-mapped IPv6 address.
Use netip.Addr.Unmap to get the address without the IPv6 prefix.
func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error)
ReadMsgUDP reads a message from c, copying the payload into b and
the associated out-of-band data into oob. It returns the number of
bytes copied into b, the number of bytes copied into oob, the flags
that were set on the message and the source address of the message.
The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
used to manipulate IP-level socket options in oob.
ReadMsgUDPAddrPort is like ReadMsgUDP but returns an netip.AddrPort instead of a UDPAddr.
func (c *UDPConn) RemoteAddr() Addr
RemoteAddr returns the remote network address.
The Addr returned is shared by all invocations of RemoteAddr, so
do not modify it.
SetDeadline implements the Conn SetDeadline method.
func (c *UDPConn) SetReadBuffer(bytes int) error
SetReadBuffer sets the size of the operating system’s
receive buffer associated with the connection.
SetReadDeadline implements the Conn SetReadDeadline method.
func (c *UDPConn) SetWriteBuffer(bytes int) error
SetWriteBuffer sets the size of the operating system’s
transmit buffer associated with the connection.
SetWriteDeadline implements the Conn SetWriteDeadline method.
SyscallConn returns a raw network connection.
This implements the syscall.Conn interface.
Write implements the Conn Write method.
func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error)
WriteMsgUDP writes a message to addr via c if c isn’t connected, or
to c’s remote address if c is connected (in which case addr must be
nil). The payload is copied from b and the associated out-of-band
data is copied from oob. It returns the number of payload and
out-of-band bytes written.
The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
used to manipulate IP-level socket options in oob.
WriteMsgUDPAddrPort is like WriteMsgUDP but takes a netip.AddrPort instead of a UDPAddr.
WriteTo implements the PacketConn WriteTo method.
package main import ( "log" "net" ) func main() { // Unlike Dial, ListenPacket creates a connection without any // association with peers. conn, err := net.ListenPacket("udp", ":0") if err != nil { log.Fatal(err) } defer conn.Close() dst, err := net.ResolveUDPAddr("udp", "192.0.2.1:2000") if err != nil { log.Fatal(err) } // The connection can write data to the desired address. _, err = conn.WriteTo([]byte("data"), dst) if err != nil { log.Fatal(err) } }
Output:
WriteToUDP acts like WriteTo but takes a UDPAddr.
WriteToUDPAddrPort acts like WriteTo but takes a netip.AddrPort.
UnixAddr represents the address of a Unix domain socket end point.
ResolveUnixAddr returns an address of Unix domain socket end point.
The network must be a Unix network name.
See func Dial for a description of the network and address
parameters.
Network returns the address’s network name, «unix», «unixgram» or
«unixpacket».
UnixConn is an implementation of the Conn interface for connections
to Unix domain sockets.
DialUnix acts like Dial for Unix networks.
The network must be a Unix network name; see func Dial for details.
If laddr is non-nil, it is used as the local address for the
connection.
ListenUnixgram acts like ListenPacket for Unix networks.
The network must be «unixgram».
func (c *UnixConn) Close() error
Close closes the connection.
CloseRead shuts down the reading side of the Unix domain connection.
Most callers should just use Close.
func (c *UnixConn) CloseWrite() error
CloseWrite shuts down the writing side of the Unix domain connection.
Most callers should just use Close.
File returns a copy of the underlying os.File.
It is the caller’s responsibility to close f when finished.
Closing c does not affect f, and closing f does not affect c.
The returned os.File’s file descriptor is different from the connection’s.
Attempting to change properties of the original using this duplicate
may or may not have the desired effect.
func (c *UnixConn) LocalAddr() Addr
LocalAddr returns the local network address.
The Addr returned is shared by all invocations of LocalAddr, so
do not modify it.
Read implements the Conn Read method.
ReadFrom implements the PacketConn ReadFrom method.
ReadFromUnix acts like ReadFrom but returns a UnixAddr.
func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAddr, err error)
ReadMsgUnix reads a message from c, copying the payload into b and
the associated out-of-band data into oob. It returns the number of
bytes copied into b, the number of bytes copied into oob, the flags
that were set on the message and the source address of the message.
Note that if len(b) == 0 and len(oob) > 0, this function will still
read (and discard) 1 byte from the connection.
func (c *UnixConn) RemoteAddr() Addr
RemoteAddr returns the remote network address.
The Addr returned is shared by all invocations of RemoteAddr, so
do not modify it.
SetDeadline implements the Conn SetDeadline method.
func (c *UnixConn) SetReadBuffer(bytes int) error
SetReadBuffer sets the size of the operating system’s
receive buffer associated with the connection.
SetReadDeadline implements the Conn SetReadDeadline method.
func (c *UnixConn) SetWriteBuffer(bytes int) error
SetWriteBuffer sets the size of the operating system’s
transmit buffer associated with the connection.
SetWriteDeadline implements the Conn SetWriteDeadline method.
SyscallConn returns a raw network connection.
This implements the syscall.Conn interface.
Write implements the Conn Write method.
func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err error)
WriteMsgUnix writes a message to addr via c, copying the payload
from b and the associated out-of-band data from oob. It returns the
number of payload and out-of-band bytes written.
Note that if len(b) == 0 and len(oob) > 0, this function will still
write 1 byte to the connection.
WriteTo implements the PacketConn WriteTo method.
WriteToUnix acts like WriteTo but takes a UnixAddr.
type UnixListener struct { }
UnixListener is a Unix domain socket listener. Clients should
typically use variables of type Listener instead of assuming Unix
domain sockets.
ListenUnix acts like Listen for Unix networks.
The network must be «unix» or «unixpacket».
Accept implements the Accept method in the Listener interface.
Returned connections will be of type *UnixConn.
AcceptUnix accepts the next incoming call and returns the new
connection.
func (l *UnixListener) Addr() Addr
Addr returns the listener’s network address.
The Addr returned is shared by all invocations of Addr, so
do not modify it.
Close stops listening on the Unix address. Already accepted
connections are not closed.
File returns a copy of the underlying os.File.
It is the caller’s responsibility to close f when finished.
Closing l does not affect f, and closing f does not affect l.
The returned os.File’s file descriptor is different from the
connection’s. Attempting to change properties of the original
using this duplicate may or may not have the desired effect.
SetDeadline sets the deadline associated with the listener.
A zero time value disables the deadline.
func (l *UnixListener) SetUnlinkOnClose(unlink bool)
SetUnlinkOnClose sets whether the underlying socket file should be removed
from the file system when the listener is closed.
The default behavior is to unlink the socket file only when package net created it.
That is, when the listener and the underlying socket file were created by a call to
Listen or ListenUnix, then by default closing the listener will remove the socket file.
but if the listener was created by a call to FileListener to use an already existing
socket file, then by default closing the listener will not remove the socket file.
SyscallConn returns a raw network connection.
This implements the syscall.Conn interface.
The returned RawConn only supports calling Control. Read and
Write return an error.
type UnknownNetworkError string
-
On JS and Windows, the FileConn, FileListener and
FilePacketConn functions are not implemented. -
On JS, methods and functions related to
Interface are not implemented. -
On AIX, DragonFly BSD, NetBSD, OpenBSD, Plan 9 and
Solaris, the MulticastAddrs method of Interface is not implemented. -
On every POSIX platform, reads from the «ip4» network
using the ReadFrom or ReadFromIP method might not return a complete
IPv4 packet, including its header, even if there is space
available. This can occur even in cases where Read or ReadMsgIP
could return a complete packet. For this reason, it is recommended
that you do not use these methods if it is important to receive a
full packet.The Go 1 compatibility guidelines make it impossible for us to
change the behavior of these methods; use Read or ReadMsgIP
instead. -
On JS and Plan 9, methods and functions related
to IPConn are not implemented. -
On Windows, the File method of IPConn is not
implemented. -
On DragonFly BSD and OpenBSD, listening on the
«tcp» and «udp» networks does not listen for both IPv4 and IPv6
connections. This is due to the fact that IPv4 traffic will not be
routed to an IPv6 socket — two separate sockets are required if
both address families are to be supported.
See inet6(4) for details. -
On Windows, the Write method of syscall.RawConn
does not integrate with the runtime’s network poller. It cannot
wait for the connection to become writeable, and does not respect
deadlines. If the user-provided callback returns false, the Write
method will fail immediately. -
On JS and Plan 9, the Control, Read and Write
methods of syscall.RawConn are not implemented. -
On JS and Windows, the File method of TCPConn and
TCPListener is not implemented. -
On Plan 9, the ReadMsgUDP and
WriteMsgUDP methods of UDPConn are not implemented. -
On Windows, the File method of UDPConn is not
implemented. -
On JS, methods and functions related to UDPConn are not
implemented. -
On JS and Plan 9, methods and functions related
to UnixConn and UnixListener are not implemented. -
On Windows, methods and functions related to UnixConn
and UnixListener don’t work for «unixgram» and «unixpacket».
The Go Blog
Introduction
If you have written any Go code you have probably encountered the built-in error
type.
Go code uses error
values to indicate an abnormal state.
For example, the os.Open
function returns a non-nil error
value when
it fails to open a file.
func Open(name string) (file *File, err error)
The following code uses os.Open
to open a file.
If an error occurs it calls log.Fatal
to print the error message and stop.
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
// do something with the open *File f
You can get a lot done in Go knowing just this about the error
type,
but in this article we’ll take a closer look at error
and discuss some
good practices for error handling in Go.
The error type
The error
type is an interface type. An error
variable represents any
value that can describe itself as a string.
Here is the interface’s declaration:
type error interface {
Error() string
}
The error
type, as with all built in types,
is predeclared
in the universe block.
The most commonly-used error
implementation is the errors
package’s unexported errorString
type.
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
You can construct one of these values with the errors.New
function.
It takes a string that it converts to an errors.errorString
and returns
as an error
value.
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
Here’s how you might use errors.New
:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// implementation
}
A caller passing a negative argument to Sqrt
receives a non-nil error
value (whose concrete representation is an errors.errorString
value).
The caller can access the error string (“math:
square root of…”) by calling the error
’s Error
method,
or by just printing it:
f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}
The fmt package formats an error
value by calling its Error() string
method.
It is the error implementation’s responsibility to summarize the context.
The error returned by os.Open
formats as “open /etc/passwd:
permission denied,” not just “permission denied.” The error returned by
our Sqrt
is missing information about the invalid argument.
To add that information, a useful function is the fmt
package’s Errorf
.
It formats a string according to Printf
’s rules and returns it as an error
created by errors.New
.
if f < 0 {
return 0, fmt.Errorf("math: square root of negative number %g", f)
}
In many cases fmt.Errorf
is good enough,
but since error
is an interface, you can use arbitrary data structures as error values,
to allow callers to inspect the details of the error.
For instance, our hypothetical callers might want to recover the invalid
argument passed to Sqrt
.
We can enable that by defining a new error implementation instead of using
errors.errorString
:
type NegativeSqrtError float64
func (f NegativeSqrtError) Error() string {
return fmt.Sprintf("math: square root of negative number %g", float64(f))
}
A sophisticated caller can then use a type assertion
to check for a NegativeSqrtError
and handle it specially,
while callers that just pass the error to fmt.Println
or log.Fatal
will
see no change in behavior.
As another example, the json
package specifies a SyntaxError
type that the json.Decode
function returns
when it encounters a syntax error parsing a JSON blob.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
The Offset
field isn’t even shown in the default formatting of the error,
but callers can use it to add file and line information to their error messages:
if err := dec.Decode(&val); err != nil {
if serr, ok := err.(*json.SyntaxError); ok {
line, col := findLine(f, serr.Offset)
return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
}
return err
}
(This is a slightly simplified version of some actual code
from the Camlistore project.)
The error
interface requires only a Error
method;
specific error implementations might have additional methods.
For instance, the net package returns errors of type error
,
following the usual convention, but some of the error implementations have
additional methods defined by the net.Error
interface:
package net
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
Client code can test for a net.Error
with a type assertion and then distinguish
transient network errors from permanent ones.
For instance, a web crawler might sleep and retry when it encounters a temporary
error and give up otherwise.
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
continue
}
if err != nil {
log.Fatal(err)
}
Simplifying repetitive error handling
In Go, error handling is important. The language’s design and conventions
encourage you to explicitly check for errors where they occur (as distinct
from the convention in other languages of throwing exceptions and sometimes catching them).
In some cases this makes Go code verbose,
but fortunately there are some techniques you can use to minimize repetitive error handling.
Consider an App Engine
application with an HTTP handler that retrieves a record from the datastore
and formats it with a template.
func init() {
http.HandleFunc("/view", viewRecord)
}
func viewRecord(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
record := new(Record)
if err := datastore.Get(c, key, record); err != nil {
http.Error(w, err.Error(), 500)
return
}
if err := viewTemplate.Execute(w, record); err != nil {
http.Error(w, err.Error(), 500)
}
}
This function handles errors returned by the datastore.Get
function and
viewTemplate
’s Execute
method.
In both cases, it presents a simple error message to the user with the HTTP
status code 500 (“Internal Server Error”).
This looks like a manageable amount of code,
but add some more HTTP handlers and you quickly end up with many copies
of identical error handling code.
To reduce the repetition we can define our own HTTP appHandler
type that includes an error
return value:
type appHandler func(http.ResponseWriter, *http.Request) error
Then we can change our viewRecord
function to return errors:
func viewRecord(w http.ResponseWriter, r *http.Request) error {
c := appengine.NewContext(r)
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
record := new(Record)
if err := datastore.Get(c, key, record); err != nil {
return err
}
return viewTemplate.Execute(w, record)
}
This is simpler than the original version,
but the http package doesn’t understand
functions that return error
.
To fix this we can implement the http.Handler
interface’s ServeHTTP
method on appHandler
:
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}
The ServeHTTP
method calls the appHandler
function and displays the
returned error (if any) to the user.
Notice that the method’s receiver, fn
, is a function.
(Go can do that!) The method invokes the function by calling the receiver
in the expression fn(w, r)
.
Now when registering viewRecord
with the http package we use the Handle
function (instead of HandleFunc
) as appHandler
is an http.Handler
(not an http.HandlerFunc
).
func init() {
http.Handle("/view", appHandler(viewRecord))
}
With this basic error handling infrastructure in place,
we can make it more user friendly.
Rather than just displaying the error string,
it would be better to give the user a simple error message with an appropriate HTTP status code,
while logging the full error to the App Engine developer console for debugging purposes.
To do this we create an appError
struct containing an error
and some other fields:
type appError struct {
Error error
Message string
Code int
}
Next we modify the appHandler type to return *appError
values:
type appHandler func(http.ResponseWriter, *http.Request) *appError
(It’s usually a mistake to pass back the concrete type of an error rather than error
,
for reasons discussed in the Go FAQ,
but it’s the right thing to do here because ServeHTTP
is the only place
that sees the value and uses its contents.)
And make appHandler
’s ServeHTTP
method display the appError
’s Message
to the user with the correct HTTP status Code
and log the full Error
to the developer console:
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
c := appengine.NewContext(r)
c.Errorf("%v", e.Error)
http.Error(w, e.Message, e.Code)
}
}
Finally, we update viewRecord
to the new function signature and have it
return more context when it encounters an error:
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
c := appengine.NewContext(r)
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
record := new(Record)
if err := datastore.Get(c, key, record); err != nil {
return &appError{err, "Record not found", 404}
}
if err := viewTemplate.Execute(w, record); err != nil {
return &appError{err, "Can't display record", 500}
}
return nil
}
This version of viewRecord
is the same length as the original,
but now each of those lines has specific meaning and we are providing a
friendlier user experience.
It doesn’t end there; we can further improve the error handling in our application. Some ideas:
-
give the error handler a pretty HTML template,
-
make debugging easier by writing the stack trace to the HTTP response when the user is an administrator,
-
write a constructor function for
appError
that stores the stack trace for easier debugging, -
recover from panics inside the
appHandler
,
logging the error to the console as “Critical,” while telling the user “a
serious error has occurred.” This is a nice touch to avoid exposing the
user to inscrutable error messages caused by programming errors.
See the Defer, Panic, and Recover
article for more details.
Conclusion
Proper error handling is an essential requirement of good software.
By employing the techniques described in this post you should be able to
write more reliable and succinct Go code.
Слышатся сирены. Студенты и учителя быстро выскочили из классов и столпились у точки сбора. Никакой опасности в поле зрения нет и ничего не горит. Это очередная учебная пожарная тревога. В случае реальной чрезвычайной ситуации все точно будут знать, что делать.
Премиум 👑 канал по Golang
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Подписаться на канал
Уроки, статьи и Видео
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Go в ВК
ЧАТ в Telegram
Содержание статьи
- Исправление ошибок в Golang
- Элегантная обработка ошибок
- Запись данных в файле
- Применяем defer — отложенные действия
- Креативная обработка ошибок
- Новые ошибки в программе на Golang
- Причины каждой ошибки в Go
- Настраиваемые типы ошибок
- Множество ошибок в Golang
- Утверждение типа Go
- Принцип работы panic
- Есть ли исключения в Golang?
- Как использовать panic
- Тонкости работы с panic в Go
Файл не найден, неверный формат, сервер недоступен. Что делает программа, когда что-то идет не так? Возможно, проблему можно решить, и тогда операции будут выполняться должным образом. Иногда лучше всего просто выйти и закрыть двери — или на крайний случай разбить окно и выскочить наружу.
План всегда важен. Рассмотрим возможные ошибки и способы их исправления. Go всегда ответственно подходит к вопросу устранения ошибок, побуждая вас задуматься о причинах ошибки, что поможет ее решить. Как и десятая пожарная тренировка, обработка ошибок может показаться монотонной, однако результат того стоит.
В данном уроке будут рассмотрены способы исправления ошибок и выяснения причины их появления. Под конец сравним стиль исправления ошибок Go и в других языках программирования.
В начала 18 века английский поэт Александр Поуп написал поэму, строчка которой известна по сей день: to err is human, то есть человеку свойственно ошибаться. Подумайте, как данную строку можно сравнить с программированием.
To err is human; to forgive, divine.
Александ Поуп, “An Essay on Criticism: Part 2”
Все делают ошибки. Системы не исключение. Ошибки повсеместны. Они не считаются редким явлением, поэтому лучше быть готовым. Принимайте ошибки, не игнорируйте их. Работайте над исправлением и двигайтесь дальше.
Исправление ошибок в Golang
В языках программирования прошлого лимитация на одно возвращаемое значение делали исправление ошибок не совсем понятным. Функции переполняли бы одинаковое возвращаемое значение для указания как ошибки, так и успешного значения, или запрашивали бы побочный канал для обращения к ошибки вроде глобальной переменной errno
. Что еще хуже, механизм сообщения об ошибках было непоследовательным от функции к функции.
У Go есть несколько возвращаемых значений, как упоминалось в уроке о функциях. Хотя это не относится к обработке ошибок, несколько возвращаемых значений обеспечивают простой и последовательный механизм возврата ошибки к вызову функций. Если функция может вернуть ошибку, соглашение состоит в том, чтобы использовать последнее возвращаемое значение для ошибок. Вызывающий элемент должен проверить, произошла ли ошибка сразу после вызова функции. Если ошибок не было, значение ошибки будет равно nil
.
Чтобы продемонстрировать обработку ошибок, Листинг 1 вызывает функцию ReadDir
. Если возникает ошибка, переменная err
не будет равна nil, что заставит программу вывести ошибку и немедленно завершить работу. Ненулевое значение, переданное os.Exit
, сообщает операционной системе, что произошла ошибка.
Если ReadDir
успешно выполнена, files
будет назначен к срезу os.FileInfo
, предоставляющий информацию о файлах и каталогах по указанному пути. В данном случае точка уточняет путь, указывающий текущую директорию.
files, err := ioutil.ReadDir(«.») if err != nil { fmt.Println(err) os.Exit(1) } for _, file := range files { fmt.Println(file.Name()) } |
Когда возникает ошибка, не стоит полагаться на другие возвращаемые значения. Они могут быть нулевыми для своего типа, но некоторые функции возвращают данные частично или же совершенно иные значения.
При запуске Листинга 1 на Go Playground в выводе будет список директорий:
Для создания списка содержимого другой директории замените текущую директорию ("."
) в Листинге 1 названием другой директории вроде "etc"
. Список может содержать как файлы, так и директории. Вы можете использовать file.IsDir()
для того, чтобы различить их.
Вопросы для проверки:
- Переделайте Листинг 1 для чтения воображаемой директории под названием
"unicorns"
. Какая ошибка выйдет? - Какое сообщение об ошибке выйдет при использовании
ReadDir
над файлом"/etc/hosts"
вместо директории.
Элегантная обработка ошибок в Golang
Разработчикам Go рекомендуется учитывать и обрабатывать любые ошибки, которые возвращают функции. Количество кода для обработки ошибок увеличивается довольно быстро. К счастью, есть несколько способов уменьшить размер кода обработки ошибок без ущерба надежности.
Некоторые функции выполняют вычисления, преобразования данных и другую логику, где ошибки будут некстати. Есть функции, которые взаимодействуют с файлами, базами данных и серверами. Связь несколько ненадежна и может потерпеть неудачу. Одной из стратегий уменьшения кода обработки ошибок является изоляция безошибочного подмножества программы от изначально подверженного ошибкам кода.
Но как насчет кода, который возвращает ошибки? Мы не можем удалить ошибки, но можем упростить код обработки ошибок. Чтобы продемонстрировать это, напишем небольшую программу для записи в файл следующих английских слоганов Go (Go Proverbs), а затем улучшим обработку ошибок, пока код не станет приемлемым.
Errors are values.
Don’t just check errors, handle them gracefully.
Don’t panic.
Make the zero value useful.
The bigger the interface, the weaker the abstraction.
interface{} says nothing.
Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.
Documentation is for users.
A little copying is better than a little dependency.
Clear is better than clever.
Concurrency is not parallelism.
Don’t communicate by sharing memory, share memory by communicating.
Channels orchestrate; mutexes serialize.Rob Pike, Go Proverbs
Запись данных в файле
При записи файла множество вещей может пойти не так. Если путь неправильный, или есть проблемы с разрешением, столкнуться с ошибкой можно еще перед началом записи. По окончании записи на диске устройства может закончится место, или же съемный диск может быть случайно извлечен. В дополнении ко всему в конце файл должен быть закрыт, это позволяет убедиться, что все успешно записано на диск, а также избежать утечки информации.
На заметку: Операционная система ограничивает число открытых файлов, поэтому при открытии каждого нового файла память уменьшается. Когда файл не используется, но при этом остается открытым, расход ресурсов является примером утечки.
Главная функция в Листинге 2 вызывает proverbs
для создания файла и обрабатывает любые ошибки, отображая ее и затем выходя. Другая имплементация может обрабатывать ошибки иначе, возможно, подсказывая пользователю другой путь или название файла. Хотя функция proverbs
может быть написана так, чтобы она выходила при возникновении ошибок, полезно разрешить вызывающему элементу решать, как обрабатывать ошибки.
err := proverbs(«proverbs.txt») if err != nil { fmt.Println(err) os.Exit(1) } |
Функция proverbs
может вернуть error
, что является специальным встроенным типом для ошибок. Функция повременит с созданием файла из за ошибки. Если в данный момент возникает ошибка, нет нужды закрывать файл, поэтому процесс обрывается. Оставшаяся часть функции записывает строки в файл и гарантирует, что файл закрыт, независимо от его успешности, как показано в коде ниже.
func proverbs(name string) error { f, err := os.Create(name) if err != nil { return err } _, err = fmt.Fprintln(f, «Errors are values.») if err != nil { f.Close() return err } _, err = fmt.Fprintln(f, «Don’t just check errors, handle them gracefully.») f.Close() return err } |
В предыдущем коде много моментов для обработки ошибок — так много, что запись каждого выражения из «Go Proverbs» может стать довольно утомительной.
Плюсом является тот факт, что у кода для обработки ошибок есть отступ, это позволяет легче ориентироваться в коде. Отступы для ошибок являются общим паттерном Go, что во время имплементации можно усовершенствовать.
Вопрос для проверки:
Почему функции должны возвращать ошибку вместо выхода из программы?
Применяем defer — отложенные действия в Golang
Убедиться, что файл правильно закрыт, можно с помощью ключевого слова defer
. Go гарантирует, что все отложенные действия будут выполнены до возврата содержащей функции. В следующем листинге каждый возвращаемый оператор, следующий за defer
, приведет к вызывающему методу f.Close()
.
func proverbs(name string) error { f, err := os.Create(name) if err != nil { return err } defer f.Close() _, err = fmt.Fprintln(f, «Errors are values.») if err != nil { return err } _, err = fmt.Fprintln(f, «Don’t just check errors, handle them gracefully.») return err } |
На заметку: Поведение предыдущего кода похоже тому, что в Листинге 3. Изменение кода без изменения его поведения называется рефакторингом. Как и переосмысление первого черновика сочинения, рефакторинг является важным навыком для написания лучшего кода.
Вы можете отложить любую функцию или метод, и как множество возвращаемых значений, отсрочка нужна не для уточнения обработки ошибки. Она улучшает обработку ошибок, избавляясь от необходимости постоянно помнить об очистке. Благодаря defer
, код для обработки ошибок может сфокусироваться только на своей задачи и больше ни о чем.
Ключевое слово defer
облегчает процесс, однако проверка на наличие ошибок после каждой строки кода очень утомительна. Пришло время для креативности!
Вопрос для проверки:
Когда будет вызвано отсроченное действие?
Креативная обработка ошибок в Golang
15 января 2015 года в блоге Go была опубликована отличная статья об обработке ошибок. В статье описывается простой способ для записи в файл без повторения одинакового кода для обработки ошибок после каждой строчки.
Для применения данной техники вам понадобится объявить новый тип, что вызывается в Листинге 5. Если при записи safeWriter
в файл возникает ошибка, он сохраняет ошибку вместо ее возвращения. Следующие попытки записи в одинаковый файл будут пропущены, если writeln
видит, что раньше была ошибка.
type safeWriter struct { w io.Writer err error // Место для хранения первой ошибки } func (sw *safeWriter) writeln(s string) { if sw.err != nil { return // Пропускает запись, если раньше была ошибка } _, sw.err = fmt.Fprintln(sw.w, s) // Записывает строку и затем хранить любую ошибку } |
Через использование safeWriter
следующий листинг записывает несколько строк без репетативной обработки ошибок, но по-прежнему возвращает все возникшие ошибки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func proverbs(name string) error { f, err := os.Create(name) if err != nil { return err } defer f.Close() sw := safeWriter{w: f} sw.writeln(«Errors are values.») sw.writeln(«Don’t just check errors, handle them gracefully.») sw.writeln(«Don’t panic.») sw.writeln(«Make the zero value useful.») sw.writeln(«The bigger the interface, the weaker the abstraction.») sw.writeln(«interface{} says nothing.») sw.writeln(«Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.») sw.writeln(«Documentation is for users.») sw.writeln(«A little copying is better than a little dependency.») sw.writeln(«Clear is better than clever.») sw.writeln(«Concurrency is not parallelism.») sw.writeln(«Don’t communicate by sharing memory, share memory by communicating.») sw.writeln(«Channels orchestrate; mutexes serialize.») return sw.err // Возвращает ошибку в случае ее возникновения } |
Это более простой и чистый способ для записи текстового файла, но смысл не в этом. Такая же техника может использоваться для создания zip-файлов или для совершенно разных задач. Крупная идея становится значительнее одной техники:
… ошибки являются значениями, и сила языка программирования Go в том, что он может обработать их.
Роб Пайк, «Ошибки — это значения«
Перед вами элегантный способ обработки ошибок в Go.
Вопрос для проверки:
Если бы сообщение об ошибке в Листинге 6 сообщало файлу “Clear is better than clever.”, какие бы событие последовали за этим?
Новые ошибки в программе на Golang
Если функция получает неверные параметры, или если что-то другое идет не так, вы можете создать и вернуть новые значения ошибок, чтобы оповестить вызывающий элемент о проблеме.
Для демонстрации новых ошибок Листинг 7 создает основу для Судоку, что представляет собой сетку 9 на 9. Каждый квадрат сетки может содержать цифру от 1 до 9. Имплементация использует массив с фиксированным размером, ноль указывает на пустой квадрат.
const rows, columns = 9, 9 // Grid является сеткой Судоку type Grid [rows][columns]int8 |
Пакет errors содержит функцию конструктора, что принимает строку для сообщения об ошибке. Используя ее, метод Set
в «Листинге 8» может создать и возвратить ошибку "out of bounds"
.
Проверка параметров в начале метода защищает оставшуюся часть метода от неправильного ввода.
func (g *Grid) Set(row, column int, digit int8) error { if !inBounds(row, column) { return errors.New(«out of bounds») } g[row][column] = digit return nil } |
Функция inBounds
в следующем листинге помогает убедиться, что row
и column
находятся в пределах границ сетки. Она не дает методу Set
забиться лишними деталями.
func inBounds(row, column int) bool { if row < 0 || row >= rows { return false } if column < 0 || column >= columns { return false } return true } |
Наконец функция main
в следующем листинге создает сетку и отображает любую ошибку, возникшую в результате неправильной замены.
func main() { var g Grid err := g.Set(10, 0, 5) if err != nil { fmt.Printf(«An error occurred: %v.n», err) os.Exit(1) } } |
На заметку: Для сообщений ошибок часто используются части предложений, чтобы перед отображением к ним можно было добавить дополнительный текст.
Всегда читайте сообщения об ошибках. Рассматривайте их как часть пользовательского интерфейса программы, будь он для конечных пользователей и разработчиков. Фраза «out of bounds» неплоха, но более точное «outside of grid boundaries» может быть лучше. А сообщение «error 37» вообще ни о чем не говорит.
Вопрос для проверки:
В чем преимущество защит от плохого ввода перед функцией?
Причины каждой ошибки в Go
Многие пакеты Go объявляют и экспортируют переменные для ошибок, которые они могут вернуть. Для использования этого с сеткой Судоку следующий листинг объявляет две переменные для ошибок на уровне пакета.
var ( ErrBounds = errors.New(«out of bounds») ErrDigit = errors.New(«invalid digit») ) |
На заметку: Принято присваивать сообщения об ошибках переменным, что начинаются со слова
Err
.
По объявлении ErrBounds
вы можете изменить метод Set
для возвращения его вместо создания новой ошибки, как показано в следующем коде.
if !inBounds(row, column) { return ErrBounds } |
Если метод Set
возвращает ошибку, вызывающая сторона может различить возможные ошибки и обрабатывать определенные ошибки по-разному, как показано в следующем листинге. Вы можете сравнить ошибку, возвращаемую с переменными ошибки, используя ==
или оператор switch.
var g Grid err := g.Set(0, 0, 15) if err != nil { switch err { case ErrBounds, ErrDigit: fmt.Println(«Les erreurs de paramètres hors limites.») default: fmt.Println(err) } os.Exit(1) } |
На заметку: Конструктор
errors.New
имплементируется через использование указателя, поэтому операторswitch
в предыдущем примере сравнивает адреса памяти, текст не содержит сообщения об ошибке.
Задание для проверки:
Напишите функцию validDigit
и используйте ее, чтобы убедиться, что метод Set
принимает только цифры между 1 и 9.
Настраиваемые типы ошибок в Golang
Каким бы полезным не был errors.New
, иногда нужно, чтобы ошибки описывались не просто сообщением. Go достаточно свободен в этом плане.
Тип error
является встроенным интерфейсом, как показано в следующем примере. Любой тип, что имплементирует метод Error()
для возвращения строки, неявно удовлетворяет интерфейс. В качестве интерфейса возможно создать новые типы ошибок.
type error interface { Error() string } |
Множество ошибок в Golang
Есть несколько причин, по которым цифра не может быть помещена в определенное место в Судоку. В предыдущем разделе мы установили два правила: строки и столбцы находятся внутри сеткии, и цифры находятся в промежутке от 1 до 9. Что будет, если вызывающий элемент передаст множество неверных аргументов?
Вместо возвращения одной ошибки за раз, метод Set
может сделать несколько проверок и вернуть все ошибки сразу. Тип SudokuError
в Листинге 15 является срезом error
. Он удовлетворяет интерфейсу error
с методом, что соединяет ошибки вместе в одну строку.
На заметку: Принято, что настраиваемые типы ошибок вроде
SudokuError
заканчиваются словомError
. Иногда это просто словоError
вродеurl.Error
из пакетаurl
.
type SudokuError []error // Error возвращает одну или несколько ошибок через запятые. func (se SudokuError) Error() string { var s []string for _, err := range se { s = append(s, err.Error()) // Конвертирует ошибки в строки } return strings.Join(s, «, «) } |
Чтобы использовать SudokuError
, метод Set
можно модифицировать для валидации границ и цифр, возвращая обе ошибки сразу, как показано в следующем примере.
func (g *Grid) Set(row, column int, digit int8) error { // Возвращает тип ошибки var errs SudokuError if !inBounds(row, column) { errs = append(errs, ErrBounds) } if !validDigit(digit) { errs = append(errs, ErrDigit) } if len(errs) > 0 { return errs } g[row][column] = digit return nil // Возвращает nil } |
Если ошибок нет, метод Set
возвращает nil
. Это не изменилось по сравнению с Листингом 8, но важно отметить, что пустой срез errs
здесь не возвращается. Для подробностей можете почитать об интерфейсах nil.
Сигнатура метода для Set
также не изменилась по сравнению с Листингом 8. Всегда используйте тип интерфейса error
при возвращении ошибок, а не конкретные типы вроде SudokuError
.
Вопрос для проверки:
Что произойдет, если метод Set
успешно вернет пустой срез errs
?
Утверждение типа в Go
Так как Листинг 16 конвертирует SudokuError
в тип интерфейса error
перед его возвращением, может возникнуть вопрос, как получить доступ к отдельным ошибкам. Решением станет утверждение типа, или type assertion. Используя утверждение типа, вы можете конвертировать интерфейс в конкретный базовый тип.
Утверждение типа в Листинге 17 утверждает err
для типа SudokuError
через код err.(SudokuError)
. Это так и есть, то ok
будет истинным, а err
будет SudokuError
, давая доступ к срезам ошибок в данном случае. Помните, что отдельные ошибки для SudokuError
являются переменными ErrBounds
и ErrDigit
, что могут сравниваться в случае необходимости.
var g Grid err := g.Set(10, 0, 15) if err != nil { if errs, ok := err.(SudokuError); ok { fmt.Printf(«%d error(s) occurred:n», len(errs)) for _, e := range errs { fmt.Printf(«- %vn», e) } } os.Exit(1) } |
В выводе предыдущего кода будут следующие ошибки:
2 error(s) occurred: — out of bounds — invalid digit |
На заметку: Если тип удовлетворяет нескольким интерфейсам, утверждение типа также может конвертировать из одного интерфейса в другой.
Вопрос для проверки:
Что делает утверждение типа err.(SudokuError)
?
Принцип работы panic в Golang
При обработке ошибок некоторые языки программирования сильно полагаются на исключения. В Go нет исключений, но в нем есть похожий механизм, который называется panic
. При задействовании panic
в программе происходит сбой. Это похоже на случаи необработанных исключений в других языках.
Есть ли исключения в Golang?
Исключения в других языках значительно отличаются от значений ошибок в Go. Это заметно как в поведении, так и в имплементации.
Если функция выдает исключение, и никто не собирается его перехватить, это исключение доходит до вызывающей функции, затем до вызывающей ту функцию и так далее, пока достигает вершины стека вызовов (например, функция main
).
Исключения — это стиль обработки ошибок, который можно считать включенным. Часто они не занимают код, тогда как выбор обработки исключений может привлекать изрядное количество специализированного кода. Это связано с тем, что вместо использования существующих возможностей языка исключения обычно имеют специальные ключевые слова, такие как try
, catch
, throw
, finally
, raise
, rescue
, except
и так далее.
Значения ошибок в Go предоставляют простую, гибкую альтернативу исключениям, которые могут помочь вам создать надежное программное обеспечение. Игнорирование значений ошибок в Go — это сознательное решение, которое становится очевидным каждому, кто читает полученный код.
Вопрос для проверки:
В чем преимущество значений ошибок Go по сравнению с исключениями?
Как использовать panic в Golang
Как упоминалось ранее, в Go есть механизм, похожий на исключения, что называется panic
. В то время, как неправильная цифра в Судоку в другом языке может стать причиной исключения, panic
в Go является редкостью.
При осознании того, что, отправившись в поездку, вы забыли полотенце, вы можете запаниковать. Аргумент, переданный panic
, может быть любого типа, не только строкой, как показано ниже:
panic(«Я забыл свое полотенце») |
На заметку: Хотя значения ошибок обычно предпочтительнее
panic
,panic
часто лучше, чемos.Exit
в том, чтоpanic
запустит любую отсроченную функцию, аos.Exit
этого делать не станет.
В некоторых ситуациях Go предпочтет panic
вместо значений ошибок, это может быть деление на ноль:
var zero int _ = 42 / zero // Runtime error: integer divide by zero — целое число делится на ноль |
Вопрос для проверки:
Как программа может использовать panic
?
Тонкости работы с panic в Golang
Чтобы panic
не привел к сбою программы, Go предоставляет функцию recover
, что показано в Листинге 18.
Отсроченные функции выполняются перед возвращением функции, даже в том случае, если задействуется panic
. Если отсроченная функция вызывает recover
, panic
остановится, и программа продолжит выполняться. В таком случае цель у recover похожа на catch
, except
и rescue
в других языках.
defer func() { if e := recover(); e != nil { // Приходит в себя после panic fmt.Println(e) // Выводит: Я забыл свое полотенце } }() panic(«Я забыл свое полотенце») // Приводит к panic |
Данный код использует анонимную функцию.
Вопрос для проверки:
Где может использоваться встроенная функция recover
?
Заключение
- Ошибки являются значениями, что внутренне оперируют с несколькими возвращаемыми значениями и другой частью языка Go;
- Будучи креативным, можно найти множество способов для обработки ошибок;
- Настраиваемые типы ошибок могут удовлетворить интерфейсу
error
; - Ключевое слово
defer
помогает выполнить очистку перед возвращением функции; - Утверждение типа может конвертировать интерфейс в конкретный тип или другой интерфейс;
- Не паникуйте — изучите ошибку.
Итоговое задание для проверки:
В стандартной библиотеке Go есть функция для парсинга веб адресов (см. golang.org/pkg/net/url/#Parse). Отобразите ошибку, которая возникает, когда url.Parse
используется для неправильного веб адреса вроде того, что содержит пробелы: https://a b.com/
.
Используйте специальный символ %#v
с Printf для изучения ошибки. Затем выполните утверждение типа *url.Error
для получения доступа и вывода полей базовой структуры.
На заметку: URL, или Uniform Resource Locator — адрес страницы в Интернете.
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
Introduction
In part I of this post, we learned about the error interface and how the standard library provides support for creating error interface values via the errors package. We also learned how to work with error interface values and use them to identify when an error has occured. Finally, we saw how some packages in the standard library export error interface variables to help us identify specific errors.
Knowing when to create and use custom error types in Go can sometimes be confusing. In most cases, the traditional error interface value provided by the errors package is enough for reporting and handling errors. However, sometimes the caller needs extra context in order to make a more informed error handling decision. For me, that is when custom error types make sense.
In this post, we are going to learn about custom error types and look at two use cases from the standard library where they are used. Each use case provides an interesting perspective for when and how to implement a custom error type. Then we will learn how to identify the concrete type of the value or pointer stored within an error interface value, and see how that can help us make more informed error handling decisions.
The net Package
The net package has declared a custom error type called OpError. Pointers of this struct are used by many of the functions and methods inside the package as the concrete type stored within the returned error interface value:
Listing 1.1
http://golang.org/src/pkg/net/dial.go
01 func Listen(net, laddr string) (Listener, error) {
02 la, err := resolveAddr(«listen», net, laddr, noDeadline)
03 if err != nil {
04 return nil, &OpError{Op: «listen», Net: net, Addr: nil, Err: err}
05 }
06 var l Listener
07 switch la := la.toAddr().(type) {
08 case *TCPAddr:
09 l, err = ListenTCP(net, la)
10 case *UnixAddr:
11 l, err = ListenUnix(net, la)
12 default:
13 return nil, &OpError{Op: «listen», Net: net, Addr: la, Err: &AddrError{Err: «unexpected address type», Addr: laddr}}
14 }
15 if err != nil {
16 return nil, err // l is non-nil interface containing nil pointer
17 }
18 return l, nil
19 }
Listing 1.1 shows the implementation of the Listen function from the net package. We can see that on lines 04 and 13, pointers of the OpError struct are created and passed in the return statement for the error interface value. Since pointers of the OpError struct implement the error interface, the pointer can be stored in an error interface value and returned. What you don’t see is that on lines 09 and 11, the ListenTCP and ListenUnix functions can also return pointers of the OpError struct, which are stored in the returned error interface value.
Next, let’s look at the declaration of the OpError struct:
Listing 1.2
http://golang.org/pkg/net/#OpError
01 // OpError is the error type usually returned by functions in the net
02 // package. It describes the operation, network type, and address of
03 // an error.
04 type OpError struct {
05 // Op is the operation which caused the error, such as
06 // «read» or «write».
07 Op string
08
09 // Net is the network type on which this error occurred,
10 // such as «tcp» or «udp6».
11 Net string
12
13 // Addr is the network address on which this error occurred.
14 Addr Addr
15
16 // Err is the error that occurred during the operation.
17 Err error
18 }
Listing 1.2 shows the declaration of the OpError struct. The first three fields on lines 07, 11 and 14 provide context about the network operation being performed when an error occurs. The fourth field on line 17 is declared as an error interface type. This field will contain the actual error that occurred and the value of the concrete type in many cases will be a pointer of type errorString.
Another thing to note is the naming convention for custom error types. It is idiomatic in Go to postfix the name of a custom error type with the word Error. We will see this naming convention used again in other packages.
Next, let’s look at the implementation of the error interface for the OpError struct:
Listing 1.3
http://golang.org/src/pkg/net/net.go
01 func (e *OpError) Error() string {
02 if e == nil {
03 return «<nil>»
04 }
05 s := e.Op
06 if e.Net != «» {
07 s += » » + e.Net
08 }
09 if e.Addr != nil {
10 s += » » + e.Addr.String()
11 }
12 s += «: » + e.Err.Error()
13 return s
14 }
The implementation of the error interface in listing 1.3 shows how the context associated with the error is used to generate a contextual error message. Binding context with the error provides extended error information and can help the caller make more informed decisions about how to handle the error.
The json Package
The json package performs the decoding of data from JSON to native Go types and vice versa. All possible errors that can be returned from the package are generated internally. Maintaining the context associated with an error is critical for this package or it wouldn’t be able to properly report what has happened. There are several custom error types in the json package and these types can be returned by the same functions and methods.
Let’s look at one of these custom error types:
Listing 1.4
http://golang.org/src/pkg/encoding/json/decode.go
01 // An UnmarshalTypeError describes a JSON value that was
02 // not appropriate for a value of a specific Go type.
03 type UnmarshalTypeError struct {
04 Value string // description of JSON value
05 Type reflect.Type // type of Go value it could not be assigned to
06 }
07
08 func (e *UnmarshalTypeError) Error() string {
09 return «json: cannot unmarshal » + e.Value + » into Go value of type » + e.Type.String()
10 }
Listing 1.4 shows the declaration of the UnmarshalTypeError struct and the implementation of the error interface. This struct is used to report errors that occur when a value can’t be decoded into a specific Go type. The struct contains two fields, one called Value on line 04 that contains the value attempted to be decoded and one called Type on line 05 which contains the Go type that the value could not be converted to. The implementation of the error interface on line 08 takes the context of the error and produces a proper error message.
In this case, the type itself provides the context for the error. The name of this type is called UnmarshalTypeError and that is the context in which it is used. When there are errors associated with unmarshaling types, pointers of this struct are stored within the returned error interface value.
When there are invalid arguments passed into an unmarshal call, a pointer of concrete type InvalidUnmarshalError is stored within the error interface value that is returned:
Listing 1.5
http://golang.org/src/pkg/encoding/json/decode.go
01 // An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
02 // (The argument to Unmarshal must be a non-nil pointer.)
03 type InvalidUnmarshalError struct {
04 Type reflect.Type
05 }
06
07 func (e *InvalidUnmarshalError) Error() string {
08 if e.Type == nil {
09 return «json: Unmarshal(nil)»
10 }
11
12 if e.Type.Kind() != reflect.Ptr {
13 return «json: Unmarshal(non-pointer » + e.Type.String() + «)»
14 }
15 return «json: Unmarshal(nil » + e.Type.String() + «)»
16 }
Listing 1.5 shows the declaration of the InvalidUnmarshalError struct and the implementation of the error interface. Once again, the type itself provides the context for the error. The state being maintained helps to produce a proper error message and provides context to help the caller make more informed error handling decision.
Concrete Type Identification
In the case of the Unmarshal function from the json package, there is the potential for a pointer of type UnmarshalTypeError, InvalidUnmarshalError or errorString to be stored within the error interface value that is returned:
Listing 1.6
http://golang.org/src/pkg/encoding/json/decode.go
01 func Unmarshal(data []byte, v interface{}) error {
02 // Check for well-formedness.
03 // Avoids filling out half a data structure
04 // before discovering a JSON syntax error.
05 var d decodeState
06 err := checkValid(data, &d.scan)
07 if err != nil {
08 return err
09 }
10
11 d.init(data)
12 return d.unmarshal(v)
13 }
14
15 func (d *decodeState) unmarshal(v interface{}) (err error) {
16 defer func() {
17 if r := recover(); r != nil {
18 if _, ok := r.(runtime.Error); ok {
19 panic®
20 }
21 err = r.(error)
22 }
23 }()
24
25 rv := reflect.ValueOf(v)
26 if rv.Kind() != reflect.Ptr || rv.IsNil() {
27 return &InvalidUnmarshalError{reflect.TypeOf(v)}
28 }
29
30 d.scan.reset()
31 // We decode rv not rv.Elem because the Unmarshaler interface
32 // test must be applied at the top level of the value.
33 d.value(rv)
34 return d.savedError
35 }
Listing 1.6 shows how the returned error interface value for the Unmarshal call can potentially store pointers of different concrete types. On line 27, the unmarshal method returns a pointer of type InvalidUnmarshalError and then on line 34, the value of the savedError field from the decodeState variable is returned. This value can be pointers of several different concrete types.
Knowing that the json package is using the custom error type as the context for the error, how can we identify the type of the concrete value to make a more informed decision about handling the error?
Let’s start with a program that causes the Unmarshal function to return an error interface value with a concrete pointer type of UnmarshalTypeError:
Listing 1.7
http://play.golang.org/p/FVFo8mJLBV
01 package main
02
03 import (
04 «encoding/json»
05 «fmt»
06 «log»
07 )
08
09 type user struct {
10 Name int
11 }
12
13 func main() {
14 var u user
15 err := json.Unmarshal([]byte({"name":"bill"}
), &u)
16 if err != nil {
17 log.Println(err)
18 return
19 }
20
21 fmt.Println(«Name:», u.Name)
22 }
Output:
2009/11/10 23:00:00 json: cannot unmarshal string into Go value of type int
Listing 1.7 shows a sample program that attempts to unmarshal a JSON document into a Go type. The JSON document on line 15 contains a field named name with a string value of bill. Since the Name field in the user type declared on line 09 is declared as an integer, the Unmarshal function returns an error interface value stored with a concrete pointer type of UnmarshalTypeError.
Now let’s make some changes to the program in listing 1.7 so the same Unmarshal call returns an error interface value that is storing a different concrete pointer type:
Listing 1.8
http://play.golang.org/p/n8dQFeHYVp
01 package main
02
03 import (
04 «encoding/json»
05 «fmt»
06 «log»
07 )
08
09 type user struct {
10 Name int
11 }
12
13 func main() {
14 var u user
15 err := json.Unmarshal([]byte({"name":"bill"}
), u)
16 if err != nil {
17 switch e := err.(type) {
18 case *json.UnmarshalTypeError:
19 log.Printf(«UnmarshalTypeError: Value[%s] Type[%v]n», e.Value, e.Type)
20 case *json.InvalidUnmarshalError:
21 log.Printf(«InvalidUnmarshalError: Type[%v]n», e.Type)
22 default:
23 log.Println(err)
24 }
25 return
26 }
27
28 fmt.Println(«Name:», u.Name)
29 }
Output:
2009/11/10 23:00:00 json: Unmarshal(non-pointer main.user)
2009/11/10 23:00:00 InvalidUnmarshalError: Type[main.user]
The sample program in listing 1.8 has a few changes from listing 1.7. On line 15, we now pass the value of the variable u instead of its address to the Unmarshal function. This change causes the Unmarshal function to return an error interface value that is storing a concrete pointer type of InvalidUnmarshalError.
Then we do something interesting on lines 17 through 24:
Listing 1.9
17 switch e := err.(type) {
18 case *json.UnmarshalTypeError:
19 log.Printf(«UnmarshalTypeError: Value[%s] Type[%v]n», e.Value, e.Type)
20 case *json.InvalidUnmarshalError:
21 log.Printf(«InvalidUnmarshalError: Type[%v]n», e.Type)
22 default:
23 log.Println(err)
24 }
A switch statement on line 17 is added to identify the concrete type of the pointer that was stored inside the returned error interface value. Notice how the keyword type is used within the interface value conversion syntax. We are also able to extract the value of the concrete type at the same time and use it in each case statement.
The case statements on lines 18 and 20 check for the specific concrete types and perform logic associated with handling those errors. This is the idiomatic way in Go to identify the concrete type of the value or pointer stored within an error interface value from a set of concrete types.
Conclusion
The error interface values we return should contain information about a specific negative condition that has occurred within the scope of a function or method that impacts the caller. It must provide enough information so the caller can act in an informed way. Usually a simple message is enough, but sometimes the caller needs more.
We saw one case from the net package where a custom error type was declared to wrap the original error and the context associated with that error. In the json package, we saw the use of custom error types that provide both the context of the error and associated state. In both cases, the need to maintain context associated with an error was a deciding factor.
When the traditional error interface value created by the errors package provides enough context for the error, use it. It is used throughout the standard library and is usually all you need. When extra context is required to help the caller make a more informed decision, take a cue from the standard library and build your own custom error types.
Александр Тихоненко
Ведущий разработчик трайба «Автоматизация бизнес-процессов» МТС Диджитал
Механизм обработки ошибок в Go отличается от обработки исключений в большинстве языков программирования, ведь в Golang ошибки исключениями не являются. Если говорить в целом, то ошибка в Go — это возвращаемое значение с типомerror
, которое демонстрирует сбой. А с точки зрения кода — интерфейс. В качестве ошибки может выступать любой объект, который этому интерфейсу удовлетворяет.
Выглядит это так:
type error interface {
Error() string
}
В данной статье мы рассмотрим наиболее популярные способы работы с ошибками в Golang.
- Как обрабатывать ошибки в Go?
- Создание ошибок
- Оборачивание ошибок
- Проверка типов с Is и As
- Сторонние пакеты по работе с ошибками в Go
- Defer, panic and recover
- После изложенного
Чтобы обработать ошибку в Golang, необходимо сперва вернуть из функции переменную с объявленным типом error
и проверить её на nil
:
if err != nil {
return err
}
Если метод возвращает ошибку, значит, потенциально в его работе может возникнуть проблема, которую нужно обработать. В качестве реализации обработчика может выступать логирование ошибки или более сложные сценарии. Например, переоткрытие установленного сетевого соединения, повторный вызов метода и тому подобные операции.
Если метод возвращает разные типы ошибок, то их нужно проверять отдельно. То есть сначала происходит определение ошибки, а потом для каждого типа пишется свой обработчик.
В Go ошибки возвращаются и проверяются явно. Разработчик сам определяет, какие ошибки метод может вернуть, и реализовать их обработку на вызывающей стороне.
Создание ошибок
Перед тем как обработать ошибку, нужно её создать. В стандартной библиотеке для этого есть две встроенные функции — обе позволяют указывать и отображать сообщение об ошибке:
errors.New
fmt.Errorf
Метод errors.New()
создаёт ошибку, принимая в качестве параметра текстовое сообщение.
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("emit macho dwarf: elf header corrupted")
fmt.Print(err)
}
С помощью метода fmt.Errorf
можно добавить дополнительную информацию об ошибке. Данные будут храниться внутри одной конкретной строки.
package main
import (
"fmt"
)
func main() {
const name, id = "bueller", 17
err := fmt.Errorf("user %q (id %d) not found", name, id)
fmt.Print(err)
}
Такой способ подходит, если эта дополнительная информация нужна только для логирования на вызывающей стороне. Если же с ней предстоит работать, можно воспользоваться другими механизмами.
Оборачивание ошибок
Поскольку Error
— это интерфейс, можно создать удовлетворяющую ему структуру с собственными полями. Тогда на вызывающей стороне этими самыми полями можно будет оперировать.
package main
import (
"fmt"
)
type NotFoundError struct {
UserId int
}
func (err NotFoundError) Error() string {
return fmt.Sprintf("user with id %d not found", err.UserId)
}
func SearchUser(id int) error {
// some logic for search
// ...
// if not found
var err NotFoundError
err.UserId = id
return err
}
func main() {
const id = 17
err := SearchUser(id)
if err != nil {
fmt.Println(err)
//type error checking
notFoundErr, ok := err.(NotFoundError)
if ok {
fmt.Println(notFoundErr.UserId)
}
}
}
Представим другую ситуацию. У нас есть метод, который вызывает внутри себя ещё один метод. В каждом из них проверяется своя ошибка. Иногда требуется в метод верхнего уровня передать сразу обе эти ошибки.
В Go есть соглашение о том, что ошибка, которая содержит внутри себя другую ошибку, может реализовать метод Unwrap
, который будет возвращать исходную ошибку.
Также для оборачивания ошибок в fmt.Errorf
есть плейсхолдер %w
, который и позволяет произвести такую упаковку.:
package main
import (
"errors"
"fmt"
"os"
)
func main() {
err := openFile("non-existing")
if err != nil {
fmt.Println(err.Error())
// get internal error
fmt.Println(errors.Unwrap(err))
}
}
func openFile(filename string) error {
if _, err := os.Open(filename); err != nil {
return fmt.Errorf("error opening %s: %w", filename, err)
}
return nil
}
Проверка типов с Is и As
В Go 1.13 в пакете Errors появились две функции, которые позволяют определить тип ошибки — чтобы написать тот или иной обработчик:
errors.Is
errors.As
Метод errors.Is
, по сути, сравнивает текущую ошибку с заранее заданным значением ошибки:
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main() {
if _, err := os.Open("non-existing"); err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("file does not exist")
} else {
fmt.Println(err)
}
}
}
Если это будет та же самая ошибка, то функция вернёт true
, если нет — false
.
errors.As
проверяет, относится ли ошибка к конкретному типу (раньше надо было явно приводить тип ошибки к тому типу, который хотим проверить):
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main() {
if _, err := os.Open("non-existing"); err != nil {
var pathError *fs.PathError
if errors.As(err, &pathError) {
fmt.Println("Failed at path:", pathError.Path)
} else {
fmt.Println(err)
}
}
}
Помимо прочего, эти методы удобны тем, что упрощают работу с упакованными ошибками, позволяя проверить каждую из них за один вызов.
Сторонние пакеты по работе с ошибками в Go
Помимо стандартного пакета Go, есть различные внешние библиотеки, которые расширяют функционал. При принятии решения об их использовании следует отталкиваться от задачи — использование может привести к падению производительности.
В качестве примера можно посмотреть на пакет pkg/errors
. Одной из его способностей является логирование stack trace:
package main
import (
"fmt"
"github.com/pkg/errors"
)
func main() {
err := errors.Errorf("whoops: %s", "foo")
fmt.Printf("%+v", err)
}
// Example output:
// whoops: foo
// github.com/pkg/errors_test.ExampleErrorf
// /home/dfc/src/github.com/pkg/errors/example_test.go:101
// testing.runExample
// /home/dfc/go/src/testing/example.go:114
// testing.RunExamples
// /home/dfc/go/src/testing/example.go:38
// testing.(*M).Run
// /home/dfc/go/src/testing/testing.go:744
// main.main
// /github.com/pkg/errors/_test/_testmain.go:102
// runtime.main
// /home/dfc/go/src/runtime/proc.go:183
// runtime.goexit
// /home/dfc/go/src/runtime/asm_amd64.s:2059
Defer, panic and recover
Помимо ошибок, о которых позаботился разработчик, в Go существуют аварии (похожи на исключительные ситуации, например, в Java). По сути, это те ошибки, которые разработчик не предусмотрел.
При возникновении таких ошибок Go останавливает выполнение программы и начинает раскручивать стек вызовов до тех пор, пока не завершит работу приложения или не найдёт функцию обработки аварии.
Для работы с такими ошибками существует механизм «defer, panic, recover»
Defer
Defer
помещает все вызовы функции в стек приложения. При этом отложенные функции выполняются в обратном порядке — независимо от того, вызвана паника или нет. Это бывает полезно при очистке ресурсов:
package main
import (
"fmt"
"os"
)
func main() {
f := createFile("/tmp/defer.txt")
defer closeFile(f)
writeFile(f)
}
func createFile(p string) *os.File {
fmt.Println("creating")
f, err := os.Create(p)
if err != nil {
panic(err)
}
return f
}
func writeFile(f *os.File) {
fmt.Println("writing")
fmt.Fprintln(f, "data")
}
func closeFile(f *os.File) {
fmt.Println("closing")
err := f.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %vn", err)
os.Exit(1)
}
}
Panic
Panic
сигнализирует о том, что код не может решить текущую проблему, и останавливает выполнение приложения. После вызова оператора выполняются все отложенные функции, и программа завершается с сообщением о причине паники и трассировки стека.
Например, Golang будет «паниковать», когда число делится на ноль:
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.divide(0x0)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:16 +0xe6
main.divide(0x1)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x2)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x3)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x4)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x5)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.main()
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:11 +0x31
exit status 2
Также панику можно вызвать явно с помощью метода panic()
. Обычно его используют на этапе разработки и тестирования кода — а в конечном варианте убирают.
Recover
Эта функция нужна, чтобы вернуть контроль при панике. В таком случае работа приложения не прекращается, а восстанавливается и продолжается в нормальном режиме.
Recover всегда должна вызываться в функции defer
. Чтобы сообщить об ошибке как возвращаемом значении, вы должны вызвать функцию recover в той же горутине, что и паника, получить структуру ошибки из функции восстановления и передать её в переменную:
package main
import (
"errors"
"fmt"
)
func A() {
defer fmt.Println("Then we can't save the earth!")
defer func() {
if x := recover(); x != nil {
fmt.Printf("Panic: %+vn", x)
}
}()
B()
}
func B() {
defer fmt.Println("And if it keeps getting hotter...")
C()
}
func C() {
defer fmt.Println("Turn on the air conditioner...")
Break()
}
func Break() {
defer fmt.Println("If it's more than 30 degrees...")
panic(errors.New("Global Warming!!!"))
}
func main() {
A()
}
После изложенного
Можно ли игнорировать ошибки? В теории — да. Но делать это нежелательно. Во-первых, наличие ошибки позволяет узнать, успешно ли выполнился метод. Во-вторых, если метод возвращает полезное значение и ошибку, то, не проверив её, нельзя утверждать, что полезное значение корректно.
Надеемся, приведённые методы обработки ошибок в Go будут вам полезны. Читайте также статью о 5 главных ошибках Junior-разработчика, чтобы не допускать их в начале своего карьерного пути.
Welcome to tutorial no. 30 in Golang tutorial series.
What are errors?
Errors indicate any abnormal condition occurring in the program. Let’s say we are trying to open a file and the file does not exist in the file system. This is an abnormal condition and it’s represented as an error.
Errors in Go are plain old values. Just like any other built-in type such as int, float64, … error values can be stored in variables, passed as parameters to functions, returned from functions, and so on.
Errors are represented using the built-in error
type. We will learn more about the error
type later in this tutorial.
Example
Let’s start right away with an example program that tries to open a file that does not exist.
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "opened successfully")
}
Run in playground
In line no. 9 of the program above, we are trying to open the file at path /test.txt
(which will obviously not exist in the playground). The Open function of the os
package has the following signature,
func Open(name string) (*File, error)
If the file has been opened successfully, then the Open function will return the file handler and error will be nil. If there is an error while opening the file, a non-nil error will be returned.
If a function or method returns an error, then by convention it has to be the last value returned from the function. Hence the Open
function returns error
as the last value.
The idiomatic way of handling errors in Go is to compare the returned error to nil
. A nil value indicates that no error has occurred and a non-nil value indicates the presence of an error. In our case, we check whether the error is not nil
in line no. 10. If it is not nil
, we simply print the error and return from the main function.
Running this program will print
open /test.txt: No such file or directory
Perfect 😃. We get an error stating that the file does not exist.
Error type representation
Let’s dig a little deeper and see how the built in error
type is defined. error is an interface type with the following definition,
type error interface {
Error() string
}
It contains a single method with the signature Error() string
. Any type which implements this interface can be used as an error. This method provides the description of the error.
When printing the error, fmt.Println
function calls the Error() string
method internally to get the description of the error. This is how the error description was printed in line no. 11 of the above sample program.
Now that we know error
is an interface type, let’s see how we can extract more information about an error.
In the example we saw above, we have just printed the description of the error. What if we wanted the actual path of the file which caused the error. One possible way to get this is to parse the error string. This was the output of our program,
open /test.txt: No such file or directory
We can parse this error message and get the file path «/test.txt» of the file which caused the error, but this is a dirty way of doing it. The error description can change at any time in newer versions of Go and our code will break.
Is there a better way to get the file name 🤔? The answer is yes, it can be done and the Go standard library uses different ways to provide more information about errors. Let’s look at them one by one.
1. Converting the error to the underlying type and retrieving more information from the struct fields
If you read the documentation of the Open function carefully, you can see that it returns an error of type *PathError.
PathError is a struct type and its implementation in the standard library is as follows,
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
In case you are interested to know where the above source code exists, it can be found here https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/io/fs/fs.go;l=250
From the above code, you can understand that *PathError
implements the error interface
by declaring the Error() string
method. This method concatenates the operation, path, and the actual error and returns it. Thus we got the error message,
open /test.txt: No such file or directory
The Path
field of PathError
struct contains the path of the file which caused the error.
We can use the As function from errors package to convert the error to it’s underlying type. The As
function’s description talks about error chain. Please ignore it for now. We will understand how error chain and wrapping works in a separate tutorial.
A simple description of As
is that it tries to convert the error to a error type and returns either true or false indicating whether the conversion is successful or not.
A program will make things clear. Let’s modify the program we wrote above and print the path using the As
function.
package main
import (
"errors"
"fmt"
"os"
)
func main() {
f, err := os.Open("test.txt")
if err != nil {
var pErr *os.PathError
if errors.As(err, &pErr) {
fmt.Println("Failed to open file at path", pErr.Path)
return
}
fmt.Println("Generic error", err)
return
}
fmt.Println(f.Name(), "opened successfully")
}
Run in Playground
In the above program, we first check whether the error is not nil
in line no. 11 and then we use the As
function in line no. 13 to convert err
to *os.PathError
. If the conversion is successful, As
will return true
. Then we print the path using pErr.Path
in line no. 14.
If you are wondering why pErr
is a pointer, the reason is, the error interface is implemented by the pointer of PathError
and hence pErr
is a pointer. The below code shows that *PathError
implements the error interface.
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
The As
function requires the second argument to be a pointer to the type that implements the error. Hence we pass &perr
.
This program outputs,
Failed to open file at path test.txt
In case the underlying error is not of type *os.PathError
, the control will reach line no. 17 and a generic error message will be printed.
Great 😃. We have successfully used the As
function to get the file path from the error.
2. Retrieving more information using methods
The second way to get more information from the error is to find out the underlying type and get more information by calling methods on the struct type.
Let’s understand this better by means of an example.
The DNSError struct type in the standard library is defined as follows,
type DNSError struct {
...
}
func (e *DNSError) Error() string {
...
}
func (e *DNSError) Timeout() bool {
...
}
func (e *DNSError) Temporary() bool {
...
}
The DNSError
struct has two methods Timeout() bool
and Temporary() bool
which return a boolean value that indicates whether the error is because of a timeout or is it a temporary one.
Let’s write a program that converts the error to *DNSError
type and calls the above mentioned methods to determine whether the error is temporary or due to timeout.
package main
import (
"errors"
"fmt"
"net"
)
func main() {
addr, err := net.LookupHost("golangbot123.com")
if err != nil {
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
if dnsErr.Timeout() {
fmt.Println("operation timed out")
return
}
if dnsErr.Temporary() {
fmt.Println("temporary error")
return
}
fmt.Println("Generic DNS error", err)
return
}
fmt.Println("Generic error", err)
return
}
fmt.Println(addr)
}
Note: DNS lookups do not work in the playground. Please run this program in your local machine.
In the program above, in line no. 9, we are trying to get the IP address of an invalid domain name golangbot123.com
. In line no. 13 we get the underlying value of the error by using the As
function and converting it to *net.DNSError
. Then we check whether the error is due to timeout or is temporary in line nos. 14 and 18 respectively.
In our case, the error is neither temporary nor due to timeout and hence the program will print,
Generic DNS error lookup golangbot123.com: no such host
If the error was temporary or due to a timeout, then the corresponding if statement would have executed and we can handle it appropriately.
3. Direct comparison
The third way to get more details about an error is the direct comparison with a variable of type error
. Let’s understand this by means of an example.
The Glob function of the filepath
package is used to return the names of all files that matches a pattern. This function returns an error ErrBadPattern
when the pattern is malformed.
ErrBadPattern is defined in the filepath
package as a global variable.
var ErrBadPattern = errors.New("syntax error in pattern")
errors.New() is used to create a new error. We will discuss this in detail in the next tutorial.
ErrBadPattern is returned by the Glob function when the pattern is malformed.
Let’s write a small program to check for this error.
package main
import (
"errors"
"fmt"
"path/filepath"
)
func main() {
files, err := filepath.Glob("[")
if err != nil {
if errors.Is(err, filepath.ErrBadPattern) {
fmt.Println("Bad pattern error:", err)
return
}
fmt.Println("Generic error:", err)
return
}
fmt.Println("matched files", files)
}
Run in playground
In the program above we search for files of pattern [
which is a malformed pattern. We check whether the error is not nil. To get more information about the error, we directly compare it to filepath.ErrBadPattern
in line. no 11 using the Is function. Similar to As
, the Is
function works on an error chain. We will learn more about this in our next tutorial.
For the purposes of this tutorial, the Is
function can be thought of as returning true
if both the errors passed to it are the same.
The Is
returns true in line no. 12 since the error is due to a malformed pattern. This program will print,
Bad pattern error: syntax error in pattern
The standard library uses any of the above-mentioned ways to provide more information about an error. We will use these ways in the next tutorial to create our own custom errors.
Do not ignore errors
Never ever ignore an error. Ignoring errors is inviting for trouble. Let me rewrite the example which lists the name of all files that match a pattern ignoring errors.
package main
import (
"fmt"
"path/filepath"
)
func main() {
files, _ := filepath.Glob("[")
fmt.Println("matched files", files)
}
Run in playground
We already know from the previous example that the pattern is invalid. I have ignored the error returned by the Glob
function by using the _
blank identifier in line no. 9. I simply print the matched files in line no. 10. This program will print,
matched files []
Since we ignored the error, the output seems as if no files have matched the pattern but actually the pattern itself is malformed. So never ignore errors.
This brings us to the end of this tutorial.
In this tutorial, we discussed how to handle errors that occur in our program and also how to inspect the errors to get more information from them. A quick recap of what we discussed in this tutorial,
- What are errors?
- Error representation
- Various ways of extracting more information from errors
- Do not ignore errors
In the next tutorial, we will create our own custom errors and also add more context to our custom errors.
Thanks for reading. Please leave your comments and feedback.
Like my tutorials? Please support the content.
Next tutorial — Custom Errors
In this article, we’ll take a look at how to handle errors using build-in Golang functionality, how you can extract information from the errors you are receiving and the best practices to do so.
Error handling in Golang is unconventional when compared to other mainstream languages like Javascript, Java and Python. This can make it very difficult for new programmers to grasp Golangs approach of tackling error handling.
In this article, we’ll take a look at how to handle errors using build-in Golang functionality, how you can extract information from the errors you are receiving and the best practices to do so. A basic understanding of Golang is therefore required to follow this article. If you are unsure about any concepts, you can look them up here.
Errors in Golang
Errors indicate an unwanted condition occurring in your application. Let’s say you want to create a temporary directory where you can store some files for your application, but the directory’s creation fails. This is an unwanted condition and is therefore represented using an error.
package main
import (
"fmt"
"ioutil"
)
func main() {
dir, err := ioutil.TempDir("", "temp")
if err != nil {
return fmt.Errorf("failed to create temp dir: %v", err)
}
}
Golang represents errors using the built-in error type, which we will look at closer in the next section. The error is often returned as a second argument of the function, as shown in the example above. Here the TempDir function returns the name of the directory as well as an error variable.
Creating custom errors
As already mentioned errors are represented using the built-in error interface type, which has the following definition:
type error interface {
Error() string
}
The interface contains a single method Error() that returns an error message as a string. Every type that implements the error interface can be used as an error. When printing the error using methods like fmt.Println the Error() method is automatically called by Golang.
There are multiple ways of creating custom error messages in Golang, each with its own advantages and disadvantages.
String-based errors
String-based errors can be created using two out-of-the-box options in Golang and are used for simple errors that just need to return an error message.
err := errors.New("math: divided by zero")
The errors.New() method can be used to create new errors and takes the error message as its only parameter.
err2 := fmt.Errorf("math: %g cannot be divided by zero", x)
fmt.Errorf on the other hand also provides the ability to add formatting to your error message. Above you can see that a parameter can be passed which will be included in the error message.
Custom error with data
You can create your own error type by implementing the Error() function defined in the error interface on your struct. Here is an example:
type PathError struct {
Path string
}
func (e *PathError) Error() string {
return fmt.Sprintf("error in path: %v", e.Path)
}
The PathError implements the Error() function and therefore satisfies the error interface. The implementation of the Error() function now returns a string with the path of the PathError struct. You can now use PathError whenever you want to throw an error.
Here is an elementary example:
package main
import(
"fmt"
)
type PathError struct {
Path string
}
func (e *PathError) Error() string {
return fmt.Sprintf("error in path: %v", e.Path)
}
func throwError() error {
return &PathError{Path: "/test"}
}
func main() {
err := throwError()
if err != nil {
fmt.Println(err)
}
}
You can also check if the error has a specific type using either an if or switch statement:
if err != nil {
switch e := err.(type) {
case *PathError :
// Do something with the path
default:
log.Println(e)
}
}
This will allow you to extract more information from your errors because you can then call all functions that are implemented on the specific error type. For example, if the PathError had a second method called GetInfo you could call it like this.
e.GetInfo()
Error handling in functions
Now that you know how to create your own custom errors and extract as much information as possible from errors let’s take a look at how you can handle errors in functions.
Most of the time errors are not directly handled in functions but are returned as a return value instead. Here we can take advantage of the fact that Golang supports multiple return values for a function. Thus you can return your error alongside the normal result — errors are always returned as the last argument — of the function as follows:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0.0, errors.New("cannot divide through zero")
}
return a/b, nil
}
The function call will then look similar to this:
func main() {
num, err := divide(100, 0)
if err != nil {
fmt.Printf("error: %s", err.Error())
} else {
fmt.Println("Number: ", num)
}
}
If the returned error is not nil it usually means that there is a problem and you need to handle the error appropriately. This can mean that you use some kind of log message to warn the user, retry the function until it works or close the application entirely depending on the situation. The only drawback is that Golang does not enforce handling the retuned errors, which means that you could just ignore handling errors completely.
Take the following code for example:
package main
import (
"errors"
"fmt"
)
func main() {
num2, _ := divide(100, 0)
fmt.Println("Number: ", num2)
}
The so-called blank identifier is used as an anonymous placeholder and therefore provides a way to ignore values in an assignment and avoid compiler errors in the process. But remember that using the blank identifier instead of probably handling errors is dangerous and should not be done if it can be avoided.
Defer, panic and recover
Go does not have exceptions like many other programming languages, including Java and Javascript but has a comparable mechanism know as ,,Defer, panic and recover». Still the use-cases of panic and recover are very different from exceptions in other programming languages as they should only be used in unexpected and unrecoverable situations.
Defer
A defer statement is a mechanism used to defer a function by putting it into an executed stack once the function that contains the defer statement has finished, either normally by executing a return statement or abnormally panicking. Deferred functions will then be executed in reverse order in which they were deferred.
Take the following function for example:
func processHTML(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
ct := resp.Header.Get("Content-Type")
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
resp.Body.Close()
return fmt.Errorf("%s has content type %s which does not match text/html", url, ct)
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
// ... Process HTML ...
return nil
}
Here you can notice the duplicated resp.Body.Close call, which ensures that the response is properly closed. Once functions grow more complex and have more errors that need to be handled such duplications get more and more problematic to maintain.
Since deferred calls get called once the function has ended, no matter if it succeeded or not it can be used to simplify such calls.
func processHTMLDefer(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
ct := resp.Header.Get("Content-Type")
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
return fmt.Errorf("%s has content type %s which does not match text/html", url, ct)
}
doc, err := html.Parse(resp.Body)
// ... Process HTML ...
return nil
}
All deferred functions are executed in reverse order in which they were deferred when the function finishes.
package main
import (
"fmt"
)
func main() {
first()
}
func first() {
defer fmt.Println("first")
second()
}
func second() {
defer fmt.Println("second")
third()
}
func third() {
defer fmt.Println("third")
}
Here is the result of running the above program:
third
second
first
Panic
A panic statement signals Golang that your code cannot solve the current problem and it therefore stops the normal execution flow of your code. Once a panic is called, all deferred functions are executed and the program crashes with a log message that includes the panic values (usually an error message) and a stack trace.
As an example Golang will panic when a number is divided by zero.
package main
import "fmt"
func main() {
divide(5)
}
func divide(x int) {
fmt.Printf("divide(%d) n", x+0/x)
divide(x-1)
}
Once the divide function is called using zero, the program will panic, resulting in the following output.
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.divide(0x0)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:16 +0xe6
main.divide(0x1)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x2)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x3)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x4)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x5)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.main()
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:11 +0x31
exit status 2
You can also use the built-in panic function to panic in your own programms. A panic should mostly only be used when something happens that the program didn’t expect and cannot handle.
func getArguments() {
if len(os.Args) == 1 {
panic("Not enough arguments!")
}
}
As already mentioned, deferred functions will be executed before terminating the application, as shown in the following example.
package main
import (
"fmt"
)
func main() {
accessSlice([]int{1,2,5,6,7,8}, 0)
}
func accessSlice(slice []int, index int) {
fmt.Printf("item %d, value %d n", index, slice[index])
defer fmt.Printf("defer %d n", index)
accessSlice(slice, index+1)
}
Here is the output of the programm:
item 0, value 1
item 1, value 2
item 2, value 5
item 3, value 6
item 4, value 7
item 5, value 8
defer 5
defer 4
defer 3
defer 2
defer 1
defer 0
panic: runtime error: index out of range [6] with length 6
goroutine 1 [running]:
main.accessSlice(0xc00011df48, 0x6, 0x6, 0x6)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:29 +0x250
main.accessSlice(0xc00011df48, 0x6, 0x6, 0x5)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:31 +0x1eb
main.accessSlice(0xc00011df48, 0x6, 0x6, 0x4)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:31 +0x1eb
main.accessSlice(0xc00011df48, 0x6, 0x6, 0x3)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:31 +0x1eb
main.accessSlice(0xc00011df48, 0x6, 0x6, 0x2)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:31 +0x1eb
main.accessSlice(0xc00011df48, 0x6, 0x6, 0x1)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:31 +0x1eb
main.accessSlice(0xc00011df48, 0x6, 0x6, 0x0)
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:31 +0x1eb
main.main()
C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:9 +0x99
exit status 2
Recover
In some rare cases panics should not terminate the application but be recovered instead. For example, a socket server that encounters an unexpected problem could report the error to the clients and then close all connections rather than leaving the clients wondering what just happened.
Panics can therefore be recovered by calling the built-in recover function within a deferred function in the function that is panicking. Recover will then end the current state of panic and return the panic error value.
package main
import "fmt"
func main(){
accessSlice([]int{1,2,5,6,7,8}, 0)
}
func accessSlice(slice []int, index int) {
defer func() {
if p := recover(); p != nil {
fmt.Printf("internal error: %v", p)
}
}()
fmt.Printf("item %d, value %d n", index, slice[index])
defer fmt.Printf("defer %d n", index)
accessSlice(slice, index+1)
}
As you can see after adding a recover function to the function we coded above the program doesn’t exit anymore when the index is out of bounds by recovers instead.
Output:
item 0, value 1
item 1, value 2
item 2, value 5
item 3, value 6
item 4, value 7
item 5, value 8
internal error: runtime error: index out of range [6] with length 6defer 5
defer 4
defer 3
defer 2
defer 1
defer 0
Recovering from panics can be useful in some cases, but as a general rule you should try to avoid recovering from panics.
Error wrapping
Golang also allows errors to wrap other errors which provides the functionality to provide additional context to your error messages. This is often used to provide specific information like where the error originated in your program.
You can create wrapped errors by using the %w flag with the fmt.Errorf function as shown in the following example.
package main
import (
"errors"
"fmt"
"os"
)
func main() {
err := openFile("non-existing")
if err != nil {
fmt.Printf("error running program: %s n", err.Error())
}
}
func openFile(filename string) error {
if _, err := os.Open(filename); err != nil {
return fmt.Errorf("error opening %s: %w", filename, err)
}
return nil
}
The output of the application would now look like the following:
error running program: error opening non-existing: open non-existing: no such file or directory
As you can see the application prints both the new error created using fmt.Errorf as well as the old error message that was passed to the %w flag. Golang also provides the functionality to get the old error message back by unwrapping the error using errors.Unwrap.
package main
import (
"errors"
"fmt"
"os"
)
func main() {
err := openFile("non-existing")
if err != nil {
fmt.Printf("error running program: %s n", err.Error())
// Unwrap error
unwrappedErr := errors.Unwrap(err)
fmt.Printf("unwrapped error: %v n", unwrappedErr)
}
}
func openFile(filename string) error {
if _, err := os.Open(filename); err != nil {
return fmt.Errorf("error opening %s: %w", filename, err)
}
return nil
}
As you can see the output now also displays the original error.
error running program: error opening non-existing: open non-existing: no such file or directory
unwrapped error: open non-existing: no such file or directory
Errors can be wrapped and unwrapped multiple times, but in most cases wrapping them more than a few times does not make sense.
Casting Errors
Sometimes you will need a way to cast between different error types to for example, access unique information that only that type has. The errors.As function provides an easy and safe way to do so by looking for the first error in the error chain that fits the requirements of the error type. If no match is found the function returns false.
Let’s look at the official errors.As docs example to better understand what is happening.
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main(){
// Casting error
if _, err := os.Open("non-existing"); err != nil {
var pathError *os.PathError
if errors.As(err, &pathError) {
fmt.Println("Failed at path:", pathError.Path)
} else {
fmt.Println(err)
}
}
}
Here we try to cast our generic error type to os.PathError so we can access the Path variable that that specific error contains.
Another useful functionality is checking if an error has a specific type. Golang provides the errors.Is function to do exactly that. Here you provide your error as well as the particular error type you want to check. If the error matches the specific type the function will return true, if not it will return false.
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main(){
// Check if error is a specific type
if _, err := os.Open("non-existing"); err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("file does not exist")
} else {
fmt.Println(err)
}
}
}
After checking, you can adapt your error message accordingly.
Sources
- Golang Blog — Working with Errors in Go 1.13
- The Go Programming language book
- Golang Blog — Defer, Panic, and Recover
- LogRocket — Error handling in Golang
- GolangByExample — Wrapping and Un-wrapping of error in Go
- Golang Documentation — Package errors
Conclusion
You made it all the way until the end! I hope this article helped you understand the basics of Go error handling and why it is an essential topic in application/software development.
If you have found this helpful, please consider recommending and sharing it with other fellow developers and subscribing to my newsletter. If you have any questions or feedback, let me know using my contact form or contact me on Twitter.