Go语言精进之路:从新手到高手的编程思想、方法和技巧(1)
上QQ阅读APP看书,第一时间看更新

12.1 结构体复合字面值

使用go vet工具对Go源码进行过静态代码分析的读者可能会知道,go vet工具中内置了一条检查规则:composites。此规则用于检查源码中使用复合字面值对结构体类型变量赋值的行为。如果源码中使用了从另一个包中导入的struct类型,但却未使用field:value形式的初值构造器,则该规则认为这样的复合字面值是脆弱的。因为一旦该结构体类型增加了一个新的字段,即使是未导出的,这种值构造方式也将导致编译失败,也就是说,应该将

err = &net.DNSConfigError{err}

替换为

err = &net.DNSConfigError{Err: err}

显然,Go推荐使用field:value的复合字面值形式对struct类型变量进行值构造,这种值构造方式可以降低结构体类型使用者与结构体类型设计者之间的耦合,这也是Go语言的惯用法。在Go标准库中,通过field:value格式的复合字面值进行结构体类型变量初值构造的例子比比皆是,比如:

// $GOROOT/src/net/http/transport.go
var DefaultTransport RoundTripper = &Transport{
    Proxy: ProxyFromEnvironment,
    DialContext: (&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
        DualStack: true,
    }).DialContext,
    MaxIdleConns:          100,
    IdleConnTimeout:       90 * time.Second,
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}

// $GOROOT/src/io/pipe.go
type pipe struct {
    wrMu sync.Mutex
    wrCh chan []byte
    rdCh chan int

    once sync.Once
    done chan struct{}
    rerr onceError
    werr onceError
}

func Pipe() (*PipeReader, *PipeWriter) {
    p := &pipe{
        wrCh: make(chan []byte),
        rdCh: make(chan int),
        done: make(chan struct{}),
    }
    return &PipeReader{p}, &PipeWriter{p}
}

这种field:value形式的复合字面值初值构造器颇为强大。与之前普通复合字面值形式不同,field:value形式字面值中的字段可以以任意次序出现,未显式出现在字面值的结构体中的字段将采用其对应类型的零值。以上面的pipe类型为例,Pipe函数在使用复合字面值对pipe类型变量进行初值构造时仅对wrCh、rdCh和done进行了field:value形式的显式赋值,这样pipe结构体中的其他变量的值将为其类型的初值,如wrMu。

从上面例子中还可以看到,通过在复合字面值构造器的类型前面增加&,可以得到对应类型的指针类型变量,如上面例子中的变量p的类型即为Pipe类型指针。

复合字面值作为结构体值构造器的大量使用,使得即便采用类型零值时我们也会使用字面值构造器形式:

s := myStruct{} // 常用

而较少使用new这一个Go预定义的函数来创建结构体变量实例:

s := new(myStruct) // 较少使用

值得注意的是,不允许将从其他包导入的结构体中的未导出字段作为复合字面值中的field,这会导致编译错误。