# 1.2 Go1到Go1.10


# 1.2 Go1到Go1.10

因为Go1承诺，Go1后序的版本都保持了向前兼容的目标。不过在从Go1发展到Go1.10的过程中，语言依然是增加了一些新的特性。本节我们简单回顾Go1到Go1.10的变化。

## 1.2.1 Go1.2（2013年12月）

Go1.2最大的语言变化是切片操作时，可以设置新切片的容量。这个需求在Go1之前就被提出了，但是因为Go1修改工作较大而延期到了Go1.2才被实现。

比如下面的代码：

```go
var a = make([]int, 10)
var b = a[i:j:k]
```

其中`b`切片是从`a`切片的第`i`个元素开始到第`j`个元素前结束，`b`切片的容量为`k`（`k`指定的容量不能超出`a`切片的容量）。

为了配合切片语法的变更，`reflect`包也增加了相应的方法：

```
func (v Value) SetCap(cap int)
func (v Value) Slice3(low, high, max int) Value
```

其中`Value.SetCap`只调整切片的容量，和`a[::cap]`写法效果等价。而`Value.Slice3`在进行切片操作的同时也指定新切片的容量，和`a[low:high:max]`写法效果等价。

通过限制子切片的容量，可以将不同子切片进行安全的分割，避免子切片无意越界操作其它切片空间。

## 1.2.2 Go1.4（2014年12月）

Go1.4语言部分对`for`语法进行了加强。在Go1.3之前`for`只有下面两种写法：

```go
for i, v := range x {
	// ...
}

for i := range x {
	// ...
}
```

`for range`针对要循环变量类型的不同，产生的循环变量也有差异。在第一种写法中，如果要循环的是数组或切片类型则`i`和`v`分别表示索引的下标和元素的值，如果循环的类型是map类型时则`i`和`v`分别表示键和值，这种写法不能用于管道类型变量的迭代。而第二种循环也可以用管道变量的迭代，直到管道被关闭时结束。如果用第二种方式循环遍历数组或map，则和`for i, _ := range x {}`的写法相关相同，相当于忽略的要迭代的值。

但是有时候我们仅仅是要循环几次而并不关心循环变量的值，在Go1.3之前可以这样写：

```go
var times [5][0]int

for i := 0; i < len(times); i++ {
	// ...
}

for _ = range times {
	// ...
}
```

前一种方式采用传统的`for`循环方式遍历，而后一种方式采用`for range`遍历，但是获取了每次遍历到的值。

在Go1.4中，后一种方式可以省略掉前面的垃圾桶变量，像这样写：

```go
var times [5][0]int

for range times {
	// ...
}
```

其中`times`对应一个`[5][0]int`类型的数组，虽然第一维数组有长度，但是数组的元素`[0]int`大小是`0`，因此整个数组占用的内存大小依然是`0`。没有付出额外的内存代价，我们就通过`for range`方式实现了`times`次快速迭代。


## 1.2.4 Go1.7（2016年8月）

在Go1.3的时代（2014年），Go语言官方博客专门属文引入了`context`概念包，并稍后在`golang.org/x/net/context`提供了官方的实现。`context`包是Go语言官方对Go进行并发编程的实践成果，用来简化对于处理单个请求的多个Goroutine之间与请求域的数据、超时和退出等操作。`context`包推出后就被社区快速吸收使用，例如gRPC以及很多Web框架都通过`context`来控制Goroutine的生命周期。

在Go1.7发布时，作为扩展包的`golang.org/x/net/context`终于移到标准库中。Go语言官方博客已经有专文讲述了`context`包的使用，这里就不详细展开了。感兴趣的读者可以查看并发相关的文档和书籍。


## 1.2.5 Go1.8（2017年2月）

Go1.8语言有一个小的变化，如果两个结构体成员名字和底层的类型相同（忽略成员的标签字符串差异），那么结构体底层对应相同的结构可以相互强制转型。

比如下面的代码：

```go
func main() {
	type T1 struct {
		X int `json:"foo"`
	}
	type T2 struct {
		X int `json:"bar"`
	}

	var v1 = T1{X: 9527}
	var v2 = T2(v1) // now legal

	fmt.Println(v2)
}
```

`T1`和`T2`仅仅是成员标签字符串不同，但是底层结构是相同的，它们可以相互强制转型。

## 1.2.6 Go1.9（2017年8月）

Go1.9终于引入了类型别名的特性。类型别名的特性如下：

```go
type T1 = T2
```

类型别名`T1`是通过`=`符号从`T2`定义，这里的`T1`和`T2`是完全相同的类型。

Go语言的接口是一大亮点特性，接口是方法的集合，而方法正是依附于类型的函数。而类型别名的一个特殊的地方是，`T1`并不是一个新的类型，因此我们不能再为`T1`定义任何新的方法。

之所以引入类型别名，很大的原因是为了解决Go1.7将`context`扩展库移动到标准库带来的问题。因为标准库和扩展库中分别定义了`context.Context`类型，而不同包中的类型是不相容的。而gRPC等很多开源的库使用的是最开始以来的扩展库中的`context.Context`类型，结果导致其无法和Go1.7标准库中的`context.Context`类型兼容。这个问题最终通过类型别名解决了：扩展库中的`context.Context`类型是标准库中`context.Context`的别名类型，从而实现了和标准库的兼容。

类型别名虽然是为了解决特定问题而引入的补丁特性。但是从类型别名我们可以发现一些有趣的用法:

```go
type ReaderA interface {
	Read(p []byte) (n int, err error)
}
type ReaderB = interface {
	Read(p []byte) (n int, err error)
}
```

上面定义的两个读接口都有同样的方法集合。而Go语言的接口是采用隐式的转义，因此可能有人会觉得这两种写法根本没有什么意义！

但是接口本身也是一种类型，如果我们基于`ReaderA`和`ReaderB`类型继续构造新的方法，就产生了差异：

```go
type MakeReaderA interface {
	MakeReader() ReaderA
}
type MakeReaderB interface {
	MakeReader() ReaderB
}
```

虽然接口定义方法的名字相同，但是方法返回的是两种不同的类型，因此方法的签名是不同的，所以说上面的两个接口并不相同。

我们现在考虑通过类型别名的方式定义一个`ReaderC`接口，然后定义一个`MakeReaderC`接口：

```go
type ReaderC = interface {
	Read(p []byte) (n int, err error)
}
type MakeReaderC interface {
	MakeReader() ReaderC
}
```

比较神奇的是`MakeReaderC`和`MakeReaderB`接口可能是等价的，因为它定义的方法名和签名都是相同的。`MakeReader`方法返回的都是一个匿名的`interface { Read(p []byte) (n int, err error) }`接口类型。而Go语言中，所有的结构相同的匿名类型其实是同一个类型。

如果通过类型别名从匿名接口构造接口，就可以避免新定义的不同接口类型对接口的方法签名造成影响。
