error数据类型
在前面的例子中,已经大量地用到了error这个数据类型。而且虽然未加说明,已经能够蒙着用这个数据类型。——编程经常就是凭着感觉蒙着干活,而且这代码还能够很好地工作。最后再查阅资料,找到佐证,心理上再得到一番安慰/踏实。
严格来讲,与其说error类型,不如说error接口。其定义如下:
type error interface {
Error() string
}
既然是接口,那么在使用的时候,遇到的都是error的实现。接下来,以打开文件为例,对error展开说明。
os.Open()
godoc
D:\examples>godoc cmd/os Open
type File struct {
// contains filtered or unexported fields
}
File represents an open file descriptor.
func Open(name string) (*File, error)
Open opens the named file for reading. If successful, methods on the
returned file can be used for reading; the associated file descriptor
has mode O_RDONLY. If there is an error, it will be of type *PathError.
PathError
如同godoc所述,当Open出错的时候,对应的是PathError类型,其为error接口的一个实现。代码如下(src\os\error.go):
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
这里PathError struct实现了error接口的func Error() string方法,所以说PathError是error的一个实现。PathError中,有一个Err error成员,自然地,这又是某个error实现。
Open()实现代码
Open() - src/os/file.go
// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
OpenFile() - src/os/file_windows.go
// OpenFile is the generalized open call; most users will use Open
// or Create instead. It opens the named file with specified flag
// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful,
// methods on the returned File can be used for I/O.
// If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
if name == "" {
return nil, &PathError{"open", name, syscall.ENOENT}
}
r, errf := openFile(name, flag, perm)
if errf == nil {
return r, nil
}
r, errd := openDir(name)
if errd == nil {
if flag&O_WRONLY != 0 || flag&O_RDWR != 0 {
r.Close()
return nil, &PathError{"open", name, syscall.EISDIR}
}
return r, nil
}
return nil, &PathError{"open", name, errf}
}
可以看到OpenFile()返回的的确是PathError类型。注意到return语句中的&。这在Go-interface中有说明。
syscall.EXXXXX - src/syscall/errors_plan9.go
// Errors
var (
EINVAL = NewError("bad arg in system call")
ENOTDIR = NewError("not a directory")
EISDIR = NewError("file is a directory")
ENOENT = NewError("file does not exist")
EEXIST = NewError("file already exists")
EMFILE = NewError("no free file descriptors")
EIO = NewError("i/o error")
ENAMETOOLONG = NewError("file name too long")
EINTR = NewError("interrupted")
EPERM = NewError("permission denied")
EBUSY = NewError("no free devices")
ETIMEDOUT = NewError("connection timed out")
EPLAN9 = NewError("not supported by plan 9")
// The following errors do not correspond to any
// Plan 9 system messages. Invented to support
// what package os and others expect.
EACCES = NewError("access permission denied")
EAFNOSUPPORT = NewError("address family not supported by protocol")
)
这里给出的是PathError的err Error成员,的确也是一个error实现。——当然,需要进一步确定NewError()。这又是什么鬼?
NewError() - src/syscall/syscall_plan9.go
NewError()是一个函数,把一个字符串转换成为一个ErrorString对象。
// NewError converts s to an ErrorString, which satisfies the Error interface.
func NewError(s string) error { return ErrorString(s) }
ErrorString() - src/syscall/syscall_plan9.go
ErrorString既是string,又是error对象。——这里的type类似于C/C++的typedef。
// ErrorString implements Error's String method by returning itself.
type ErrorString string
func (e ErrorString) Error() string { return string(e) }
小结
现在转了一大圈,就是把PathError以及PathError中的err对象全部过了一遍。结论就是PathError是error实现,ErrorString是error实现。——其实整个Go中,有大量的error实现。但只限于注意其只有一个func Error() string方法即可,其返还error的详细信息。
fmt.Println(err)
示例代码
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("abcdefg.xyz")
if err != nil {
fmt.Println("err: ", err)
fmt.Println("Error():", err.Error())
} else {
fmt.Println("Open OK.")
file.Close()
}
}
输出结果
D:\examples>go run helloworld.go
err: open abcdefg.xyz: The system cannot find the file specified.
Error(): open abcdefg.xyz: The system cannot find the file specified.
D:\examples>
Println()的入参
注意到Println()的两种入参:
fmt.Println("err: ", err)
fmt.Println("Error():", err.Error())
打印结果是一样的。
难不成Go也有Java一样的toString()方法?事实上,Go比Java更加智能,关于这一块,不再展开(目前还没有分析透彻),只给出src/fmt/print.go的如下代码。简单说,就是会利用反射机制,自动调用error接口的Error()方法,打印这个方法的返回值。所以,以上两种入参的效果是一样的。当然,后者的性能更优。
func (p *pp) printArg(arg interface{}, verb rune) {
//...
// Some types can be done without reflection.
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
//...
case string:
p.fmtString(f, verb)
case []byte:
p.fmtBytes(f, verb, "[]byte")
case reflect.Value:
p.printValue(f, verb, 0)
default:
// If the type is not simple, it might have methods.
if !p.handleMethods(verb) {
// Need to use reflection, since the type had no
// interface methods that could be used for formatting.
p.printValue(reflect.ValueOf(f), verb, 0)
}
}
}
实用函数
上面描述的内容似乎和日常编码没有多大关系,接下来聊聊一些实用方面。
package errors
源代码
这个包的内容很少,直接拷贝过来(省略掉版权信息):
// Package errors implements functions to manipulate errors.
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
也就是说,errors这个包只有一个对外可见的函数,即New(),其返还一个实现了error接口的对象(即errorString实现)。
示例代码
package main
import (
"fmt"
"errors"
)
func myadd(x, y int) (ret int, err error) {
if x <= 0 || y <= 0 {
err = errors.New("x or y less or equal 0!")
return
} else {
ret = x + y
err = nil
return
}
}
func test(x, y int) {
ret, err := myadd(x, y)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(ret)
}
}
func main() {
test(0, 1)
test(1, 0)
test(-1, 1)
test(1, -1)
test(1, 1)
}
运行结果:
D:\examples>go run helloworld.go
x or y less or equal 0!
x or y less or equal 0!
x or y less or equal 0!
x or y less or equal 0!
2
D:\examples>
fmt.Errorf()
这是更加实用的一个函数。
源代码 - src/fmt/print.go
// Sprintf formats according to a format specifier and returns the resulting string.
func Sprintf(format string, a ...interface{}) string {
p := newPrinter()
p.doPrintf(format, a)
s := string(p.buf)
p.free()
return s
}
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
示例
只需要把上例myadd中的一句话替换为://哦,还要注释掉errors的import。
err = fmt.Errorf("x(%d) or y(%d) less or equal 0!", x, y)
运行结果:
D:\examples>go run helloworld.go
x(0) or y(1) less or equal 0!
x(1) or y(0) less or equal 0!
x(-1) or y(1) less or equal 0!
x(1) or y(-1) less or equal 0!
2
D:\examples>
有疑问加站长微信联系(非本文作者)