天天看点

Go-Errorserror数据类型os.Open()Open()实现代码fmt.Println(err)实用函数

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>