deep in golang type and wrapper function

这一篇的内容基本上都是来自于官方的spec,主要是记录type相关的内容。

type基本定义

type用来确定一个value的值的集合所在的范围,比如int类型的值与float类型的值的范围是不同的,因为int与float是属于两种不同的type,那为什么要有不同的类型呢?比如为什么要有int和float这种划分,这种划分的目的在于表示对于内存空间的不同占用长度。

type还能用来对value的操作进行限制,不同的type除了值不同外<占用的内存空间不同>,可以进行的操作也不同。

golang中的type主要分为两类

  • Namedtype 已经预先声明好的type,这里(https://golang.org/ref/spec#Predeclared_identifiers)列出了可以直接使用的type。
  • Unnamedtype 使用上面提到的预先声明好的type来进行定义,用type的具体语法来进行定义。这些类型被成为复合类型(Composite types)。
    每一个类型T都会有一个底层类型underlying type,如果T是一个已经定义好的Namedtype,对应的底层类型就是T本身。否则T的底层类型就是T在type declaration中所引用的那个类型的底层类型。
1
2
3
4
type T1 string
type T2 T1
type T3 []T1
type T4 T3

T1 T2 的底层类型是T1,但是T3,T4的底层类型是[]T1。

type的声明

type声明的这个行为主要是绑定一个新的标识符(identifier)也就是type name到一个新的type上。

新声明的type与之前的type有共同的底层类型。比如type IntArray [16]int 新声明的type IntArry的底层类型是[]int,通过声明绑定了标识符(identifier)到[]int上。
新声明的类型与已经存在的类型是不同的(虽然它们的底层类型是相同的),主要体现在对于不同的method绑定上。比如可以可以新声明一个type 与int类型有相同的底层类型,之后可以给这个新生成的类型绑定method。比如

1
2
3
4
5
6
7
8
9
10
type TimeZone int
const (
EST TimeZone = -(5 + iota)
CST
MST
PST
)
func (tz TimeZone) String() string {
return fmt.Sprintf("GMT%+dh", tz)
}

这样的操作就通过定义新的type,声明新的标识符(identifier)实现了对于底层类型的扩展,以上代码在原有的int的基础上声明了TimeZone并且额外生成了一个针对于TimeZone的string方法。

参考 /reflec/type.go,其中kind函数的返回类型都包括以下

1
Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer

function wrapper

这里需要特别注意一点,在上面列出的底层类型中,Func也算是一种底层类型,也就是说可以通过type关键自来为一个已经声明好的函数再指定一个identifier,这种使用方式就是通常的function wrapper。乍一看其实比较让人疑惑,感觉函数中套函数,摸不着头脑,不知道这样做的意义是什么。

按照我自己的理解,结合前面的讨论,最容易想到的就是通过这种wrapper的操作起到了对于已有函数进行扩展的功能,这种扩展是更highlevel的一层扩展,比如在wrapper操作的时候并不需要考虑原先的函数的具体实现是怎样的。

比如在net/http/server.go中

1
2
3
4
5
6
7
8
9
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

如果函数f有合适的函数签名,也就是说形参是(ResponseWriter, *Request),没有返回值,这个时候HandlerFunc(f)这种表述就可以将f这个底层类型强制转化为了HandlerFunc类型,这样由于HandlerFunc的类型本身定义了ServerHTTP方法,于是HandlerFunc这个类型就实现了http.Handler的接口,就可以通过http.Handler接口进行调用。更详细的解释可以参考这一篇(https://medium.com/@matryer/the-http-handler-wrapper-technique-in-golang-updated-bc7fbcffa702)(很赞的资料),这一篇中对wrapper机制进行了深入的探讨有很多实际的例子可以参考,wrapper函数传入的参数类型和返回的参数是同样的类型,但是比起原来的操作,再原先的函数执行的前后又加上一些额外的操作

1
An http.Handler wrapper is a function that has one input argument and one output argument, both of type http.Handler.

基本来说用到wrapper的场景主要有:

  • Logging and tracing
  • Validating the request; such as checking authentication credentials,比如身份检查通过了之后再调用serveHTTP
  • Writing common response headers(在参考的原文中,对于ResponseWriter进行wrapper操作然后同时实现了response的log操作于返回操作)
    最直观的一个例子就是之前的
1
http.HandleFunc("/path", handleThing)

这种形式的表述变成了如下形式的表述

1
2
3
4
5
6
7
8
http.HandleFunc("/path", log(handleThing))
func log(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Before")
h.ServeHTTP(w, r) // call original
log.Println("After")
})
}

参考资料

https://golang.org/ref/spec#Type_declarations
https://golang.org/ref/spec#Method_sets
https://golang.org/ref/spec#Types
https://golang.org/ref/spec#Properties_of_types_and_values
https://golang.org/ref/spec
https://www.linkedin.com/pulse/20140828174314-67956038-types-in-go-lang
https://medium.com/@matryer/the-http-handler-wrapper-technique-in-golang-updated-bc7fbcffa702
几种不同的找到object所对应的type的方法

推荐文章