Saturn

The devil is in the details.

0%

Programming language GO & RUST

Go

  1. 常量

    1. 定义格式 const Name [Type] = Value
  2. 变量

    1. 定义格式 var Name [Type]
    2. 系统自动赋予初值
    3. 全局变量希望能被外部包所使用,则需要将变量首字母大写
    4. 函数体内定义的变量为局部变量,否则为全局变量
    5. 可以省去Type,变量在被赋值时编辑器会在编译阶段做类型推断
    6. 当你在函数体内声明局部变量时,应使用简短声明语法 :=
    7. 值类型和引用类型
      1. 值类型用等号赋值的时候,实际上是在内存中做了值拷贝
        1. int float bool string 数组 struct
        2. 存储在栈内
      2. 引用类型变量存储的是值所在的内存地址
        1. 指针 slices maps channel
        2. 存储在堆中
        3. 局部变量的简短化创建形式a := 50
        4. 局部变量不可以声明了但却不使用,全局变量可以
      3. init函数
        1. 变量可以在init函数中被初始化,init函数在每个包完成初始化后自动执行,优先级比main高
  3. 基本类型和运算符

    1. bool 格式化输出时,可以用%t来表示要输出的类型为bool
    2. 数值类型:
      1. int和uint根据操作系统的位数,决定数值的长度(4位或者8位)
      2. uintptr长度被设定为足够放一个指针即可
      3. 整数:
        1. int8(-128 -> 127)
        2. int16(-32768 -> 32767)
        3. int32(-2,147,483,648 -> 2,147,483,647)
        4. int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
      4. 无符号整数:
        1. uint8(0 -> 255)
        2. uint16(0 -> 65,535)
        3. uint32(0 -> 4,294,967,295)
        4. uint64(0 -> 18,446,744,073,709,551,615)
      5. 浮点数:
        1. float32(+- 1e-45 -> +- 3.4 * 1e38):小数点后7位
        2. float64(+- 5 1e-324 -> 107 1e308):小数点后15位
      6. 复数:
        1. complex64(32位实数和虚数)
        2. Complex128(64位实数和虚数)
      7. 随机数:
        1. rand包
      8. 类型别名:
        1. type 别名 类型
      9. 字符类型
        1. char
        2. 实际存储了整型
    3. 字符串
      1. 字符串是字节的定长数组
      2. 解释字符串,用双引号括起来
      3. 非解释字符串,反引号括起来
      4. 字符串的二元运算符比较,逐个字节对比
      5. 获取字符串中某个字节的地址是非法的$str[i]
      6. 字符串使用+拼接
    4. strings和strconv包(String 库函数的使用)
    5. 指针:
      1. 一个指针变量可以指向任何一个值得内存地址
  4. 控制结构(省去了condition两侧的括号,使得代码更加整洁,执行语句中的括号在任何情况下都不能被省略)

    1. if-else
    2. if-else if-else
    3. 测试多返回值函数的错误
      1. 方法可以返回多个返回值,第二个返回值可以是错误的详细信息,如果第二个返回值不为Nil,则代表发生了错误。
    4. switch case:
      1. 不需要写break
      2. 如果希望匹配到之后还继续执行后面的分支,用“fallthrough”关键字
      3. switch 语句的第二种形式是不提供任何被判断的值(实际上默认为判断是否为 true),然后在每个 case 分支中进行测试不同的条件。当任一分支的测试结果为 true 时,该分支的代码会被执行。这看起来非常像链式的 if-else 语句,但是在测试条件非常多的情况下,提供了可读性更好的书写方式。
      4. switch的第三种形式是condition中可以对两个变量进行计算赋值。随后在case分支中根据变量的值进行具体的行为
    5. 循环:for结构
      1. 基本形式:for 初始化语句;条件语句;修饰语句{}
      2. 第二种形式,类似于while循环。没有初始化语句和index更新语句
      3. 第三种形式,无限循环。for {}
      4. for-range结构:for ix, val := range coll { }
    6. Break 和 continue
    7. label和goto(不推荐使用,没看)
  5. 函数

    1. 分类:普通的带有名字的函数、匿名函数、方法

    2. go里面函数重载是不允许的,没有泛型,为了效率

    3. 函数的一般定义:func f(name1 type1,name 2type2) 返回值类型,参数可以没有参数名。

    4. 函数都是按照值传递的

    5. 带命名的返回值,只需要在函数尾部直接return

    6. 不带命名的返回值,需要用()装起来写在return后面

    7. 空白符_匹配一些不需要的值,然后丢掉

    8. 通过传递指针来改变函数外部变量的值

    9. 变长参数函数

      1. 形式:func myFunc(a,b,arg ...int){}

      2. 如果一个变长参数的类型没有被指定,则可以使用默认的空接口 interface{},这样就可以接受任何类型的参数

        `func typecheck(..,..,values … interface{}) {

        for _, value := range values {
            switch v := value.(type) {
                case int: …
                case float: …
                case string: …
                case bool: …
                default: …
            }
        }
        

        }`

    10. defer和追踪

      1. defer作用:类似于finally,用于一些资源的释放
      2. 使用defer来记录函数的参数和返回值
    11. 将函数作为参数

      1. func IndexFunc(s string, f func(c int) bool) int
    12. 闭包

      1. 匿名函数赋值给变量:fplus := func(x, y int) int { return x + y }
      2. 直接调用匿名函数:func(x, y int) int { return x + y } (3, 4)
      3. 匿名函数的调用,在匿名函数后加一对()表示对其的调用
    13. 应用闭包:将函数作为返回值

      1. 闭包函数保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量。

      2. 在闭包中使用到的变量可以是在闭包函数体内声明的,也可以是在外部函数声明的:

        `var g int
        go func(i int) {

        s := 0
        for j := 0; j < i; j++ { s += j }
        g = s
        

        }(1000) // Passes argument 1000 to the function literal.`

  6. 数组与切片

    1. 数组

      1. 声明语句:var identifier [len]type
      2. 使用for循环遍历

      `for i:=0; i < len(arr1); i++{

      arr1[i] = ...
      

      }`

      1. 使用for-range遍历

      `for i:=0; i < len(arr1); i++{

      arr1[i] = ...
      

      }`

      1. Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new() 来创建: var arr1 = new([5]int)。arr1的类型是*[5]int,把arr1赋值给另一个时,需要做一次数组内存的拷贝。
      2. 讲数组作为函数参数时,会做一次数组的拷贝,如果需要修改传入数组的值,需要用引用传递的方式
      3. 数组可以在声明时使用{}来初始化
    2. 切片

      1. 定义和相关特性

        1. 切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型
        2. 和数组不同的是,切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个 长度可变的数组
        3. 多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块。
      2. 优点:

        1. 因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 切片比数组更常用。
      3. 声明:var identifier []type(不需要说明长度)。

      4. 初始化:

        1. var slice1 []type = arr1[start:end]头闭尾开区间
        2. 类似数组的初始化:var x = []int{2, 3, 5, 7, 11}。这样就创建了一个长度为 5 的数组并且创建了一个相关切片。
      5. 长度:存储的值的个数

      6. 容量:cap() 可以测量切片最长可以达到多少:它等于切片从第一个元素开始,到相关数组末尾的元素个数

      7. 对于每个切片,以下状态总是成立:

        s == s[:i] + s[i:] // i是一个整数且: 0 <= i <= len(s) len(s) <= cap(s)

      8. 切片的存储类似结构体:

        1. 指向相关数组的指针
        2. 长度
        3. 容量
      9. 将切片传递给函数:

        `func sum(a []int) int {

        s := 0
        for i := 0; i < len(a); i++ {
            s += a[i]
        }
        return s
        

        }

        func main() {

        var arr = [5]int{0, 1, 2, 3, 4}
        sum(arr[:])
        

        }`

      10. 使用make创造一个切片:

        1. var slice1 []type = make([]type, len)
        2. 简写为:slice1 := make([]type, len)
        3. make 的使用方式是:func make([]T, len, cap),其中 cap 是可选参数。
        4. 以下两种创建切片的方法等效:
          1. make([]int, 50, 100)
          2. new([100]int)[0:50]
      11. make和new的区别

        1. 看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。
        2. new (T) 为每个新的类型 T 分配一片内存,初始化为 0 并且返回类型为 * T 的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体(参见第 10 章);它相当于 &T{}。
        3. make(T) 返回一个类型为 T 的初始值,它只适用于 3 种内建的引用类型:切片、map 和 channel
        4. 换言之,new 函数分配内存,make 函数初始化
      12. bytes包Buffer(类似于java里面的StringBuilder)

        1. 申明方式:var buffer bytes.Buffer
        2. 获取指针:var r *bytes.Buffer = new(bytes.Buffer)
        3. 或者通过函数:func NewBuffer(buf []byte) *Buffer
      13. 切片的for-range

        1. 单维切片:

          `for ix, value := range slice1 {

          ...
          

          }`

        2. 多维切片:

          `for row := range screen {

          for column := range screen[row] {
              screen[row][column] = 1
          }
          

          }`

      14. 切片重组(扩容)

        1. 扩展一位:sl = sl[0:len(sl)+1]
      15. 切片的复制与增加

        1. 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原切片的内容都拷贝过来。

        2. 通过func append(s[]T, x ...T) []T在切片中追加内容

          1. 例子sl3 := []int{1, 2, 3} sl3 = append(sl3, 4, 5, 6)
        3. 通过拷贝讲切片复制到新的切片中func copy(dst, src []T) int,返回拷贝的元素的个数

          1. 例子:sl_from := []int{1, 2, 3}

            sl_to := make([]int, 10)

            n := copy(sl_to, sl_from)

  7. Map

    1. 声明:var map1 map[keytype]valuetype

    2. 赋值:map1[key1] = val1

    3. 取值:v := map1[key1]

    4. 获取长度:len(map1)

    5. 初始化:{key1:val1, key2:val2}

    6. make初始化:map1 := make(map[keytype]valuetype) 相当于``mapCreated := map[string]float32{}`

    7. 切片作为map的值:

      mp1 := make(map[int][]int) mp2 := make(map[int]*[]int)

    8. 检验key是否存在:

      `if _, ok := map1[key1]; ok {

      // ...
      

      }`

    9. 删除kv: delete(map1, key1)

    10. for-range遍历:

      1. kv:

        `for key, value := range map1 {

        ...
        

        }`

      2. 只关心value

        `for _, value := range map1 {

        ...
        

        }`

      3. 只关心key

        `for key := range map1 {

        fmt.Printf("key is: %d\n", key)
        

        }`

    11. 切片:

      1. 两次make,第一次分配切片,第二次分配切片中每个map元素

        `items := make([]map[int]int, 5)
        for i:= range items {

        items[i] = make(map[int]int, 1)
        items[i][1] = 2
        

        }`

    12. map排序

      1. 拷贝出key,对key排序,然后顺序遍历key取出value(会不会效率太低了?)
    13. 将kv对调:

      1. 拷贝一个新的大小相同的map,遍历原始map,复制数据到新的map
    1. 标准库
    2. regexp包
    3. sync包
    4. 紧密计算big包
    5. 自定义包和可见性:
      1. Import with . :import . “./pack1”,当使用. 来做为包的别名时,可以不通过包名来使用其中的项目。例如:test := ReturnStr()。在当前的命名空间导入 pack1 包,一般是为了具有更好的测试效果。
      2. Import with _ :import _ “./pack1”,pack1 包只导入其副作用,也就是说,只执行它的 init 函数并初始化其中的全局变量。
      3. 导入外部安装包:先通过go install安装
  8. 结构体和方法(struct & method)

    1. 结构体:

      1. 定义:

        1
        2
        3
        4
        5
        type identifier struct {
        field1 type1
        field2 type2
        ...
        }
      2. 使用new

        t := new(T)

      3. 使用声明:

        var t T:分配内存并零值化内存

      4. 使用.选择器来访问结构体的属性,无论变量是结构体还是结构体类型的指针

        1
        2
        3
        4
        5
        type myStruct struct { i int }
        var v myStruct // v是结构体类型变量
        var p *myStruct // p是指向一个结构体类型变量的指针
        v.i
        p.i
      5. 使用{}初始化一个结构体

        1
        2
        ms := &struct1{10, 15.5, "Chris"}
        // 此时ms的类型是 *struct1

        表达式 new(Type)&Type{} 是等价的。

        或者

        1
        2
        var ms struct1
        ms = struct1{10, 15.5, "Chris"}

        或者制定字段key来初始化

        1
        2
        3
        intr := Interval{0, 3}            (A)
        intr := Interval{end:5, start:1} (B)
        intr := Interval{end:5} (C)
      6. 结构体的内存布局:结构体和它所包含的数据在内存中是以连续快的形式存在的。

      7. 递归结构体:可以用来定义链表的节点或者二叉树的节点

      8. make不能用于struct

      9. 结构体可以带Tag,通过反射获取

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        package main

        import (
        "fmt"
        "reflect"
        )

        type TagType struct { // tags
        field1 bool "An important answer"
        field2 string "The name of the thing"
        field3 int "How much there are"
        }

        func main() {
        tt := TagType{true, "Barak Obama", 1}
        for i := 0; i < 3; i++ {
        refTag(tt, i)
        }
        }

        func refTag(tt TagType, ix int) {
        ttType := reflect.TypeOf(tt)
        ixField := ttType.Field(ix)
        fmt.Printf("%v\n", ixField.Tag)
        }
      10. 匿名字段和内嵌结构体

        1. 匿名字段:通过结构体名字.字段类型来访问匿名字段,没个结构体针对每一种数据类型只能有一个匿名字段
        2. 内嵌结构体:结构体可以通过结构体名字.内嵌结构体字段来访问内嵌匿名结构体的字段,类似于软件工程领域的组合设计模式
        3. 命名冲突:外层结构体的相同命名字段会覆盖内层结构体的相同命名字段,访问外层结构体的相同命名字段A.b,访问内层结构体的相同命名字段A.B.b
    2. 方法:

      1. 在 Go 语言中,结构体就像是类的一种简化形式,那么面向对象程序员可能会问:类的方法在哪里呢?在 Go 中有一个概念,它和方法有着同样的名字,并且大体上意思相同:Go 方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数。一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。

      2. 类型 T(或 *T)上的所有方法的集合叫做类型 T(或 *T)的方法集。

        因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在,比如在同一个包里这么做是允许的:

        1
        2
        func (a *denseMatrix) Add(b Matrix) Matrix
        func (a *sparseMatrix) Add(b Matrix) Matrix
      3. 定义方法的格式

        func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }

      4. 方法的调用方式:

        recv.methodName(),recv类似于面向对象语言中的this或者self

      5. 一个例子:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        package main

        import "fmt"

        type TwoInts struct {
        a int
        b int
        }

        func main() {
        two1 := new(TwoInts)
        two1.a = 12
        two1.b = 10

        fmt.Printf("The sum is: %d\n", two1.AddThem())
        fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20))

        two2 := TwoInts{3, 4}
        fmt.Printf("The sum is: %d\n", two2.AddThem())
        }

        func (tn *TwoInts) AddThem() int {
        return tn.a + tn.b
        }

        func (tn *TwoInts) AddToParam(param int) int {
        return tn.a + tn.b + param
        }
      6. 函数和方法的区别:

        1. 函数将变量作为参数:Function1(recv)

          方法在变量上被调用:recv.Method1()

        2. 方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。

      7. 指针作为接受者:

        1. 传入指针或者值都是合法的,go会自动解引用
        2. 指针方法和值方法都可以在指针或者非指针上被调用
      8. 获取或者设置对象的值使用getter和setter

      9. 多重继承可以通过一个类型内嵌多个匿名类型来实现,匿名类型的方法会被提升为此父类型的方法

      10. 总结:

        1. 在Go中,类型就是类
        2. Go拥有类似面向对象语言的嘞继承的概念以实现代码复用和多态
        3. go中代码复用通过组合和委托实现,多态用接口来实现。
        4. 类型可以覆写内嵌匿名类型的方法
  9. 接口与反射

    1. 接口

      1. 定义:接口提供了一种方式来说明对象的行为:如果谁能搞定这件事,它就可以用在这儿。

      2. 接口定义了一组方法,但是这些方法不包含实现,接口内也不能拥有变量

      3. 接口定义语法:

        1
        2
        3
        4
        5
        type Namer interface {
        Method1(param_list) return_type
        Method2(param_list) return_type
        ...
        }
      4. 类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。

        实现某个接口的类型(除了实现接口方法外)可以有其他的方法。

        一个类型可以实现多个接口。

        接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。

      5. 例子(go中的多态?)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        package main

        import "fmt"

        type Shaper interface {
        Area() float32
        }

        type Square struct {
        side float32
        }

        func (sq *Square) Area() float32 {
        return sq.side * sq.side
        }

        func main() {
        sq1 := new(Square)
        sq1.side = 5

        var areaIntf Shaper
        areaIntf = sq1
        // shorter,without separate declaration:
        // areaIntf := Shaper(sq1)
        // or even:
        // areaIntf := sq1
        fmt.Printf("The square has area: %f\n", areaIntf.Area())
        }
      6. 接口嵌套接口

        1. 一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          type ReadWrite interface {
          Read(b Buffer) bool
          Write(b Buffer) bool
          }

          type Lock interface {
          Lock()
          Unlock()
          }

          type File interface {
          ReadWrite
          Lock
          Close()
          }
      7. 类型断言:如何监测和转换接口变量的类型?

        1. 定义:一个接口类型的变量varI中可以包含任何类型的值,必须有一种方式来检测它的动态类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它总是可以分配给接口变量本身的类型。通常我们可以使用类型断言来测试某个时刻varI是否包含类型T的值:

          v := varI.(T)

          varI必须是一个接口类型变量。类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败会导致错误发生。更安全的方式是使用以下形式来进行类型断言:

          1
          2
          3
          4
          5
          if v, ok := varI.(T); ok {  // checked type assertion
          Process(v)
          return
          }
          // varI is not of type T

          如果转换合法,vvarI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,okfalse,也没有运行时错误发生。

          例子:(暂时还不能明白这个的用处2021/12/2)

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          package main

          import (
          "fmt"
          "math"
          )

          type Square struct {
          side float32
          }

          type Circle struct {
          radius float32
          }

          type Shaper interface {
          Area() float32
          }

          func main() {
          var areaIntf Shaper
          sq1 := new(Square)
          sq1.side = 5

          areaIntf = sq1
          // Is Square the type of areaIntf?
          if t, ok := areaIntf.(*Square); ok {
          fmt.Printf("The type of areaIntf is: %T\n", t)
          }
          if u, ok := areaIntf.(*Circle); ok {
          fmt.Printf("The type of areaIntf is: %T\n", u)
          } else {
          fmt.Println("areaIntf does not contain a variable of type Circle")
          }
          }

          func (sq *Square) Area() float32 {
          return sq.side * sq.side
          }

          func (ci *Circle) Area() float32 {
          return ci.radius * ci.radius * math.Pi
          }
      8. 类型判断:type-switch

        1. 接口变量的类型也可以使用type-switch结构来判断

        2. 接口类型变量可以代表任何类型,所以需要有类型判断

        3. 例子

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          func classifier(items ...interface{}) {
          for i, x := range items {
          switch x.(type) {
          case bool:
          fmt.Printf("Param #%d is a bool\n", i)
          case float64:
          fmt.Printf("Param #%d is a float64\n", i)
          case int, int64:
          fmt.Printf("Param #%d is a int\n", i)
          case nil:
          fmt.Printf("Param #%d is a nil\n", i)
          case string:
          fmt.Printf("Param #%d is a string\n", i)
          default:
          fmt.Printf("Param #%d is unknown\n", i)
          }
          }
          }

          可以这样调用此方法:classifier(13, -14.3, “BELGIUM”, complex(1, 2), nil, false) 。

          在处理来自于外部的、类型未知的数据时,比如解析诸如 JSON 或 XML 编码的数据,类型测试和转换会非常有用。

      9. 测试一个值是否实现了某个接口:

        1
        2
        3
        4
        5
        6
        7
        type Stringer interface {
        String() string
        }

        if sv, ok := v.(Stringer); ok {
        fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
        }

        接口是一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。

      10. 使用方法集与接口(这节有点晦涩)

        1. 总结

          在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以从具体类型 P 直接可以辨识的:

          1. 指针方法可以通过指针调用
          2. 值方法可以通过值调用
          3. 接收者是值的方法可以通过指针调用,因为指针会首先被解引用
          4. 接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
        2. Go 语言规范定义了接口方法集的调用规则:

          1. 类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
          2. 类型 T 的可调用方法集包含接受者为 T 的所有方法
          3. 类型 T 的可调用方法集不包含接受者为 *T 的方法
      11. 空接口

        1. 概念:不包含任何方法,它对实现不做任何要求(类似于Java中的Object对象)

        2. 可以给一个空接口类型的变量var val interface {}赋值任何类型的值

        3. 复制数据切片到空接口切片是不允许的,因为内存布局不一致,需要用for-range逐个复制

          1
          2
          3
          4
          5
          var dataSlice []myType = FuncReturnSlice()
          var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
          for i, d := range dataSlice {
          interfaceSlice[i] = d
          }
        4. 一个接口的值可以赋值给另一个接口变量,只要底层类型实现了必要的方法

    2. 反射

      1. 概念:反射是用程序检查气所拥有的的结构,尤其是类型的一种能力;这是元编程的一种形式,反射可以在运行时检查类型和变量,例如大小方法和动态的调用这些方法。

      2. Go中反射包Type用来表示一个Go类型,反射包Value为Go值提供了发射接口

      3. 两个函数:

        1
        2
        func TypeOf(i interface{}) Type
        func ValueOf(i interface{}) Value
      4. 通过反射修改或者设置值

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        package main

        import (
        "fmt"
        "reflect"
        )

        func main() {
        var x float64 = 3.4
        v := reflect.ValueOf(x)
        // setting a value:
        // v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value
        fmt.Println("settability of v:", v.CanSet())
        v = reflect.ValueOf(&x) // Note: take the address of x.
        fmt.Println("type of v:", v.Type())
        fmt.Println("settability of v:", v.CanSet())
        v = v.Elem()
        fmt.Println("The Elem of v is: ", v)
        fmt.Println("settability of v:", v.CanSet())
        v.SetFloat(3.1415) // this works!
        fmt.Println(v.Interface())
        fmt.Println(v)
        }
        //输出:
        settability of v: false
        type of v: *float64
        settability of v: false
        The Elem of v is: <float64 Value>
        settability of v: true
        3.1415
        <float64 Value>
      5. 反射结构体:

        1. ``NumField() 方法返回结构体内的字段数量
        2. 通过for循环用索引取得每个字段的值Field(i)
        3. 用签名在结构体上的方法,例如,使用索引 n 来调用:Method(n).Call(nil)
      6. 接口与动态类型:

        1. Go 中的接口跟 Java/C# 类似:都是必须提供一个指定方法集的实现。但是更加灵活通用:任何提供了接口方法实现代码的类型都隐式地实现了该接口,而不用显式地声明。

          和其它语言相比,Go 是唯一结合了接口值,静态类型检查(是否该类型实现了某个接口),运行时动态转换的语言,并且不需要显式地声明类型是否满足某个接口。该特性允许我们在不改变已有的代码的情况下定义和使用新接口。

          接收一个(或多个)接口类型作为参数的函数,其实参可以是任何实现了该接口的类型。 实现了某个接口的类型可以被传给任何以此接口为参数的函数 。

        2. 接口的继承:

          1. 当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。类型可以通过继承多个接口来提供像 多重继承 一样的特性:

            1
            2
            3
            4
            type ReaderWriter struct {
            *io.Reader
            *io.Writer
            }
    3. Go中的面相对象总结:

      1. Go 没有类,而是松耦合的类型、方法对接口的实现。
      2. 封装:
        1. 包范围内的:通过标识符首字母小写,对象 只在它所在的包内可见
        2. 可导出的:通过标识符首字母大写,对象 对所在包以外也可见
      3. 继承:
        1. 用组合实现:内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重继承可以通过内嵌多个类型实现
      4. 多态:
        1. 用接口实现:某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和接口是松耦合的,并且多重继承可以通过实现多个接口实现。Go 接口不是 Java 和 C# 接口的变体,而且:接口间是不相关的,并且是大规模编程和可适应的演进型设计的关键。
  10. 错误处理与测试:

    1. Go中预定义的error类型接口

      1
      2
      3
      type error interface {
      Error() string
      }
    2. 定义错误:

      err := errors.New(“math - square root of negative number”)

    3. 运行时异常和Panic

      1. 当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发运行时 panic,伴随着程序的崩溃抛出一个 runtime.Error 接口类型的值。这个错误值有个 RuntimeError() 方法用于区别普通错误。

        panic 可以直接从代码初始化:当错误条件(我们所测试的代码)很严苛且不可恢复,程序不能继续运行时,可以使用 panic 函数产生一个中止程序的运行时错误。panic 接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go 运行时负责中止程序并给出调试信息。

      2. Panic的调用方式:

        在多层嵌套的函数调用中调用 panic,可以马上中止当前函数的执行,所有的 defer 语句都会保证执行并把控制权交还给接收到 panic 的函数调用者。这样向上冒泡直到最顶层,并执行(每层的) defer,在栈顶处程序崩溃,并在命令行中用传给 panic 的值报告错误情况:这个终止过程就是 panicking。

    4. 从Panic中恢复

      1. panic 会导致栈被展开直到 defer 修饰的 recover () 被调用或者程序中止。

The Cargo Book

  1. Cargo Guide

    1. What is cargo?

      Cargo is the Rust package manager. It is a tool that allows Rust packages to declare their various dependencies and ensure that you’ll always get a repeatable build.

    2. Creating a new package

      1. cargo new hello_world --bin
      2. cargo build
      3. cargo run
      4. cargo build --release
    3. Working on an existing package

      1. git clone
      2. cargo build: fetch dependencies and build them
    4. Package layout

      1. Cargo.toml and Cargo.lock are stored in the root of your package
      2. src for source code
      3. src/lib.rs for default library files
      4. Other executables can be placed in src/bin/.
      5. Benchmarks go in the benches directory.
      6. Examples go in the examples directory.
      7. Integration tests go in the tests directory.
    5. toml and lock

      1. Cargo.toml is about describing your dependencies in a broad sense, and is written by you.
      2. Cargo.lock contains exact information about your dependencies. It is maintained by Cargo and should not be manually edited.
      3. cargo update will update dependencites to newest version
    6. Tests

      1. Command cargo test
      2. run unit tests in /src/tests dir
      3. run integration-style tests in /tests dir
  2. Cargo Reference

    1. Specifying Dependencies
      1. specifying dependencites from crates.io:default choice
      2. Caret requirements:an update is allowed if the new version number does not modify the lefct-most non-zero digit in the major,minor,patch grouping
      3. Tilde requirements:specify a minimal version with some ability to update(not specified part can be modified)
      4. Wildcard requirements:allow for any version where the wildcard is positioned
      5. comparison requirements: allow manually specifying a version range or an exiact version to depend on
      6. multiple requirements:eperated with comma
      7. specifying dependencies from other registries
      8. specifying depemdencies from git repositories
      9. specifying path dependencies
      10. Mutiple locations
      11. Platform specified dependencies
  3. Cargo commands

    1. General Commands
      1. cargo
      2. cargo help
      3. cargo version
    2. Build Commands
      1. cargo bench:execute benchmarks of a package
      2. cargo build:Compile the current package
      3. cargo check: check a local package and all of its dependencies for errors
      4. cargo clean:remove artifacts feom the target directory that Cargo has generated in the past
      5. cargo doc:build the documentation for the local pakage and all dependencies.the output is placed in target/doc
      6. cargo fetch:fetch dependencies of a pakage from the network
      7. cargo fix:automatically fix lint warnings reported by rustc
      8. cargo run:run binary or exaple if local package
      9. cargo rustc:copile the current package
      10. cargo rustdoc:build a pakage’s documentation
      11. cargo test: execute unit and integration test of package
    3. Manifest Commands
      1. cargo generate-lockfile:create cargo.lock file for the curren package or workspace.if already exists, rebuild the lastest avaliable version of every package
      2. cargo locate-project: print a JSON object to stdout with the full path to the Cargo.toml manifest
      3. cargo metadata:output JSON to stdout containning info about the memebers and resolved deoendencies of the current package,–format-version is recommended
      4. cargo pkgid: print out the fully qualified package ID specifier for a package or dependency in the curren workspace
      5. cargo tree:display a tree of dependencies to the terminal
      6. cargo update:update dependencies as recorded in the local lock file
      7. cargo vendor:vendor all crates.io and git dependencies for a project into the specified directory at <path>. After this command completes the vendor directory specified by <path> will contain all remote sources from dependencies specified.
      8. cargo verify-project:parse the local manifest and check it’s validity
    4. Package commands
      1. cargo init:create a new cargo manifest in the current directory.
      2. cargo install:This command manages Cargo’s local set of installed binary crates. Only packages which have executable [[bin]] or [[example]] targets can be installed, and all executables are installed into the installation root’s bin folder.
      3. cargo new:create a new cargo package in the given directory.
      4. cargo search:this performs a textual search for crates on cargo repository.The matching crates will be displayed along with their descriptioin in TOML format suitable for copying into a Cargo.html manifest.
      5. cargo uninstall:by default all binaries are removed for a crate but –bin and –example flags can be used to only remove particular binaries.
    5. Publishing Commands
      1. cargo login:This command will save the API token to disk so that commands that require authentication, such as cargo-publish(1), will be automatically authenticated. The token is saved in $CARGO_HOME/credentials.toml. CARGO_HOME defaults to .cargo in your home directory.
      2. cargo owner:This command will modify the owners for a crate on the registry. Owners of a crate can upload new versions and yank old versions. Non-team owners can also modify the set of owners, so take care!
      3. cargo package:This command will create a distributable, compressed .crate file with the source code of the package in the current directory. The resulting file will be stored in the target/package directory.
      4. cargo publish:This command will create a distributable, compressed .crate file with the source code of the package in the current directory and upload it to a registry.
      5. cargo yank:The yank command removes a previously published crate’s version from the server’s index. This command does not delete any data, and the crate will still be available for download via the registry’s download link.

The Rust Programming Language

  1. Programming a Guessing Game

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    use std::io;
    use rand::Rng;
    use std::cmp::Ordering;

    fn main(){
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1..101);
    // println!("The secret number is: {}", secret_number);
    loop {
    println!("Please input your guess.");
    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("Failed to read line");
    let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
    };
    match guess.cmp(&secret_number) {
    Ordering::Less => println!("Too small!"),
    Ordering::Greater => println!("Too big!"),
    Ordering::Equal => {
    println!("You win!");
    break;
    }
    }
    println!("You guessed :{}", guess);
    }
    }
  2. Common Programming Concepts

    1. Variables and Mutablity

      1. by default variables are immutable
      2. if want mutable, use keyword mut before name of viariance
      3. Difference between variables and constants
        1. mut can not be used with constants
        2. declare constants using const instead of let
        3. constants can be declared in any scope
        4. constants may be set only to a constant expression
        5. constants naming convention:use all upercase with underscores between words
      4. Shadowing
        1. we can shadow a variable by using the same variable’s name and repeating the use of let keyword
        2. shadowing allow us using the same name for different types
    2. Data types

      1. Scalar Types

        1. Integer Types

          Length Signed Unsigned
          8-bit i8 u8
          16-bit i16 u16
          32-bit i32 u32
          64-bit i64 u64
          128-bit i128 u128
          arch isize usize

          Range caculation: -(2^n - 1^) to 2^n - 1^ - 1

          e.g. 1_000 = 1000, 57u8 = u8 type of value 57

          more examples:

          Number literals Example
          Decimal 98_222
          Hex 0xff
          Octal 0o77
          Binary 0b1111_0000
          Byte (u8 only) b'A'
        2. Floating-Point Types

          1. single-percision:f32
          2. double-percision:f64
        3. Boolean type

          1. bool
        4. Character Type

          1. size:4 bytes
          2. Unicode
          3. char
      2. Compound Types

        1. The Tuple Type

          1. group together a number of values with a variety of types into one compound type

          2. fix length

          3. e.g.

            1
            2
            3
            fn main() {
            let tup: (i32, f64, u8) = (500, 6.4, 1);
            }
          4. visited by index

        2. The Array Type

          1. every element of an array muse have the same type

          2. fix length

          3. e.g

            1
            2
            3
            4
            5
            fn main() {
            let a = [1, 2, 3, 4, 5];
            let a: [i32; 5] = [1, 2, 3, 4, 5];
            let a = [3; 5]// size 5 with initial value 3
            }
          4. visited by index

    3. Functions

      1. Coding stype: snake case. All letters are lowercase and underscores separate words
      2. start with fn
      3. Function Parameters
        1. type of each parameter must be declared
        2. multiple parameters separated with commas
      4. Function bodies contain statements and expressions
        1. Expressions do not include ending semicolons, if have, expression turns to statement,which will not return a value
      5. Functions with return values
        1. Declare return vlaue types after a ->
        2. the return value of the function is synonymous with the value of the final expression in the block of the body of a function.
    4. Comments

      1. //
    5. Control Flow

      1. If Expressions(if-else if-else)

      2. Loop(loop)

        1
        2
        3
        4
        5
        fn main() {
        loop {
        println!("again!");
        }
        }
      3. break & continue can use lable to apply to the labled loop

      4. you can add the calue you want returned after the break expression

      5. conditional loop with while

      6. For loop:for element in collection

  3. Understanding Ownership

    1. What is ownership?

      1. Memory is managed through a system of ownership with a set of rules that the compiler checks at compile time.
      2. Stack and Heap
        1. stack for known, fixed size data at compile time
        2. heap is less organized, freee space must be serached
        3. hense stack is more efficiency
        4. when code calls a function ,value passed into function and variables get pushed onto the stack , when the function is over, those values get poped off the stack
        5. heap is controlled by ownership
      3. Ownership rules
        1. Each value in Rust has variable that’s called its owner
        2. There can only be one owner at a time
        3. When the owner goes out of scope,the value will be dropped
      4. Variable Scope
        1. When varianle comes into scope, it is valid
        2. it remains valid until it goes out of scope
      5. The String type
        1. string is immutable but String not
      6. Memory and Allocation
      7. Ways Variables and Data interact:Move
        1. primitive types allocated on stack
        2. Reference allocated on stack
        3. Data allocated on heap
        4. For ptimitive types: s1 = s2 means copy on stack
        5. For non-primitive types: s1 = s2 means referece copied on stack and data on heap did not do anything
      8. Ways Varianles and Data interact:Clone
        1. if we do want to deeply copy the heap data of the String, not just the stack data, we can use a common method called clone
      9. Stack-Only data:copy
        1. Types such as integers that have a known size at compile time are strored entirely on the stack ,so copies of the actual values are quick to make.
        2. if a type implements the copy trait, an doler variable is still usable after assignment.
      10. Ownership and functions
        1. The semantics for passing a value to a function are similar to those for assigning a value to a variable.Passing a variable to a funtion will move or Copy.For ptimitive types, after funciton calling, variable is still valid, but for other(e.g. String) types, calling function means ownership moving ,which meams s will be invalid after function call.
      11. Return values and scope
        1. returning values can also transfer ownership
        2. The ownership of a variable follows the same pattern every time: assigning a vlaue to another variable moves it.When a variable that includes data on the heap goes out of scope,the value will be cleaned by drop unless the data has been moved to be owned by another variable.
    2. References and Borrowing

      1. reference allow you to refer to some value without taking ownership of it.

      2. The &s1 syntax lets us create a reference that refers to the value of s1 but does not own it. Because it does not own it, the value it points to will not be dropped when the reference stops being used.

      3. When functions have rederences as parameters intead of the actual values, we won’t need to return the values in order to give back ownership, because we never had ownership.We call the action of creating a reference borrowing.

      4. Modifying something borrowed is not allowed, just as variables are immutable by default, so are references.

      5. Mutable references, e.g.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        fn main() {
        let mut s = String::from("hello");

        change(&mut s);
        }

        fn change(some_string: &mut String) {
        some_string.push_str(", world");
        }

        Mutable references have one big restiction:you can have only one mutable reference to a particular piece of data at a time.The restriction preventing multiple mutable references to the same data at the same time allows for mutation but in a very controlled fashion.Rust prevents data races even in compile time.Samelly, combining mutable and immutable references is also no permitted.(considered it is same as RW lock mechanism).Note that a reference’s scope starts from where it is introduced and continues through the last time that references is used.

      6. Dangling References

        In languages with pointers, it’s easy to erroneously create a dangling pointer, a pointer that references a location in memory that may have been given to someone else, by freeing some memory while preserving a pointer to that memory. In Rust, by contrast, the compiler guarantees that references will never be dangling references: if you have a reference to some data, the compiler will ensure that the data will not go out of scope before the reference to the data does.

      7. The rules of References

        1. At any given time, you can have either one mutable references or any number of immutable references.
        2. Referebces must always be valid.
    3. The Slice Type

      1. A String Slice is a reference to part of a String.
      2. We can create slices using a range within brackets by specifying [starting_index..ending_index], where starting_index is the first position in the slice and ending_index is one more than the last position in the slice.
      3. if starting_index omitted, which means start from zero, if ending_index omitted, which means end to last byte, if all ommited, which means reference the total string
      4. The type that signifies “string slice” is written as &str
      5. The compiler will ensure the references into the String remain valid.
      6. String literals are slices:let s = "Hello, World", the type of s here is &s, it is a slice poting to that specified piont of the binary, This is also why string literals are immutable, &str is an immutable reference.
  4. Using structs to structure related data

    1. Defining an instantiating structs

      1. Structs are similar to tuples, but unlike with tuple, you will name each piece of data so it is clear what the values mean.Structs are more flexible than tuples:you don’t have to rely on the order of the data so specify or access the value of an instance.
      2. kv pairs visited(read or write) by dot.
    2. Creating instances from other instances with struct update syntax

      1. Using struct update syntax, we can achieve the same effect with less code, as shown in Listing 5-7. The syntax .. specifies that the remaining fields not explicitly set should have the same value as the fields in the given instance.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        struct User {
        active: bool,
        username: String,
        email: String,
        sign_in_count: u64,
        }

        fn main() {
        let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
        };

        let user2 = User {
        email: String::from("another@example.com"),
        ..user1
        };
        }
    3. Using Tuple Structs Without Named Fields to Create Different Types

      1
      struct Color(i32, i32, i32);
    4. Unit-Like Structs Without Any Fields

      1
      2
      3
      4
      5
      fn main() {
      struct AlwaysEqual;

      let subject = AlwaysEqual;
      }
    5. Method Syntax

      1. Method are similar to functions, but methods are different from functions in that they’re defined within the context of a struct and their first parameter is always self, which represents the instance of the stuct the method is being called on.

      2. Where is the -> Operator?

        1. Rust has a featured called automatic referencing and dereferencing.
      3. Associated Functions

        1. All functions defined within an impl block are called associaterd functions because they’re associated with the type name after the impl

        2. Associated functions that aren’t methods are often used for constructors that will return a new instance of the struct.

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          #[derive(Debug)]
          struct Rectangle {
          width: u32,
          height: u32,
          }

          impl Rectangle {
          fn square(size: u32) -> Rectangle {
          Rectangle {
          width: size,
          height: size,
          }
          }
          }

          fn main() {
          let sq = Rectangle::square(3);
          }
      4. Multiple imlp Blocks

        1. It is valid to seperate methods into multiple impl blocks but not recomended.
  5. Enums and Pattern Matching

    1. Defining an Enum

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      enum IpAddrKind {
      V4,
      V6,
      }

      fn main() {
      let four = IpAddrKind::V4;
      let six = IpAddrKind::V6;

      route(IpAddrKind::V4);
      route(IpAddrKind::V6);
      }

      fn route(ip_kind: IpAddrKind) {}

    2. Enum Values

      1
      2
      3
      4
      5
      6
      7
      8
      enum IpAddrKind{
      v4(String),
      v6(String),
      }

      let home = IpAddr::V4(String::from("127.0.0.1"));

      let loopback = IpAddr::V6(String::from("::1"));

      Any type can be values for Enums.

    3. Enum Methods

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      fn main() {
      enum Message {
      Quit,
      Move { x: i32, y: i32 },
      Write(String),
      ChangeColor(i32, i32, i32),
      }

      impl Message {
      fn call(&self) {
      // method body would be defined here
      }
      }

      let m = Message::Write(String::from("hello"));
      m.call();
      }
    4. The OptionEnum and it’s advantages over Null values

      1. When we have Somevalue,we know that a value is present and the value is held within the Some
      2. When we have a Nonevalue,in some sense, it means the same thing as null
      3. In other words, you have to convert an Option<T> to a T before you can perform T operations with it.Generally, this helps catch one of the most common issues with null: assuming that something isn’t null when it actually is.
      4. match expression is a control flow construct that does just this when used with enums.
    5. The match control flow operator

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      fn main() {
      fn plus_one(x: Option<i32>) -> Option<i32> {
      match x {
      None => None,
      Some(i) => Some(i + 1),
      }
      }
      let five = Some(5);
      let six = plus_one(five);
      let none = plus_one(None);
      }
    6. Matches Are Exhaustive

      1. Matches in Rust are exhaustive, we must exhaust every last possibility in order for the code to be valid.Especially inthe case of Option<T>, when Rust prevent us from forgetting to explicitly handle None case.
    7. Catch-all Patterns and the _ Placeholder

      1. for match default arm, we can use other and _to handle this.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        fn main() {
        let dice_roll = 9;
        match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
        }

        fn add_fancy_hat() {}
        fn remove_fancy_hat() {}
        fn move_player(num_spaces: u8) {}
        }
        fn main() {
        let dice_roll = 9;
        match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => (),
        }

        fn add_fancy_hat() {}
        fn remove_fancy_hat() {}
        }
    8. Concise Control Flow with if let

      1. The if letsyntax lets you combine if and let into a less verbose way to handle values that match one pattern while ignoring the rest.

      2. In other words, you can think of if let as syntax sugar for a match that runs code when the value matches one pattern and then ignores all other values.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        fn main() {
        let config_max = Some(3u8);
        match config_max {
        Some(max) => println!("The maximum is configured to be {}", max),
        _ => (),
        }
        }
        fn main() {
        let config_max = Some(3u8);
        if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
        }
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        #[derive(Debug)]
        enum UsState {
        Alabama,
        Alaska,
        // --snip--
        }

        enum Coin {
        Penny,
        Nickel,
        Dime,
        Quarter(UsState),
        }

        fn main() {
        let coin = Coin::Penny;
        let mut count = 0;
        match coin {
        Coin::Quarter(state) => println!("State quarter from {:?}!", state),
        _ => count += 1,
        }
        }
        #[derive(Debug)]
        enum UsState {
        Alabama,
        Alaska,
        // --snip--
        }

        enum Coin {
        Penny,
        Nickel,
        Dime,
        Quarter(UsState),
        }

        fn main() {
        let coin = Coin::Penny;
        let mut count = 0;
        if let Coin::Quarter(state) = coin {
        println!("State quarter from {:?}!", state);
        } else {
        count += 1;
        }
        }

  6. Managing Growing projects with packages,Crates,and Modules

    (haven’t read)

  7. Common Collections

    1. Types:

      1. A vector allows you to store a variable number of values next to each other
      2. A string is a collection of characters
      3. A hash map allows you to associate a value with a particular key
    2. Storing Lists of Values with Vectors

      1. Creating a new vector

        let v: Vec<i32> = Vec::new()

        Simply

        let v = vec![1,2,3]

      2. Update a vector

        v.push(1)

      3. Drop a vector Drops its elements

        1
        2
        3
        4
        5
        6
        7
        fn main() {
        {
        let v = vec![1, 2, 3, 4];

        // do stuff with v
        } // <- v goes out of scope and is freed here
        }
      4. Read elements of vectors

        with index syntax or the get method

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        fn main() {
        let v = vec![1, 2, 3, 4, 5];

        let third: &i32 = &v[2];
        println!("The third element is {}", third);

        match v.get(2) {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element."),
        }
        }
      5. Iterating over the values in a vector

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        fn main() {
        let v = vec![100, 32, 57];
        for i in &v {
        println!("{}", i);
        }
        }
        fn main() {
        let mut v = vec![100, 32, 57];
        for i in &mut v {
        *i += 50;
        }
        }
      6. Using am Emum to store multiple Types

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        fn main() {
        enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
        }

        let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
        ];
        }
    3. Storing UTF-8 Encoded Text with Strings

      1. Create a new String

        let data = "initial contents".to_string()

        let s = String::from("initial contents")

      2. Update a String

        s.push_str("bar")

        s.push('l')

        1
        2
        3
        4
        let s1 = String::from("Hello, ");
        let s2 = String::from("world!");
        let s3 = s1 + &s2;
        // + use fn add(self, s: &str) -> String {

        use format! Macro is recomended

        1
        2
        3
        4
        5
        let s1 = String::from("tic");
        let s2 = String::from("tac");
        let s3 = String::from("toe");

        let s = format!("{}-{}-{}", s1, s2, s3);

        format! macro works in the same way as ptintln! but instead of printing the output to the screen,it returns a String with the contents. The code generated by the format! macro uses references so that this call doesn’t take ownership of any of its parameters.

      3. Index into Strings

        1. index to a String is not valid.
      4. Slice Strings

        s[start..end]

      5. Methods for Iterating Over Strings

        for c in "abcde".chars()

        for b in "abcde".bytes()

    4. Storing keys with associated values in hash maps

      1. Creating a hash map

        1
        2
        3
        4
        use std::collections::HashMap;
        let mut scores = HashMap::new();
        scores.inert(String::from("Blue"), 10);
        scores.indert(String::from("Yellow"),50);

        Another way with two vec

        1
        2
        3
        4
        use std::collections::HashMap;
        let teams = vec![String::from("Blue"), String::from("Yellow")];
        let initial_scores = vec![10, 50];
        let mut scores: HashMap<_, _> = teams.into_iter().zip(initial_scores.into_iter()).collect();
      2. HashMaps and ownership

        1. For types that implement the copy trait, the values are copied into the hashmap.For owned values like String, the values will be moved and the hashmao will be the owner of those values.
      3. Accessing values in a hashmap

        let team_name = String::from("Blue")

        let score = scores.get(&team_name)

        1
        2
        3
        4
        5
        6
        use std:collections::Hashmap;
        let mut scores = HashMap::new();
        scores.insert(String::from("Blue"), 10);
        for(key, value) int &scores {
        println!("{}:{}",key, value);
        }
      4. Updating a hashmap

        1. overwitring a value

          1
          2
          scores.insert(String::from("Blue"), 10);
          scores.insert(String::from("Blue"), 20);
        2. Only inserting a value if the key has no value

          1
          scores.entry(String::from("Yellow")).or_insert(50);

          The or_insert method on Entry is defined to return a mutable reference to the value for the corresponding Entry key if that key exists, and if not ,inserts the parameter as the new value for this key and returns a mutable reference to the new value.

        3. Updating a value based on the old value

          1
          2
          3
          4
          5
          6
          let text = "hello world wonderful world";
          let mut map = HashMap::new();
          for word in text,split_whitespcae() {
          let count = map.entry(word).or_intsert(0);
          *count += 1;
          }

          The or_insert method actually returns a mutable reference(&mut v) to the value for this key.

  8. Error Handling

    1. Unrecoverable Erros with panic!

    2. Recoverable Errors with Result

      1. Resuit Enum

        1
        2
        3
        4
        5
        6
        7
        8
        use std::fs::File;
        fn main(){
        let f = File::open("hello.txt");
        let f = match f {
        OK(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
        };
        }
      2. Mathcing on different errors

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        fn main() {
        let f = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
        File::create("hello.txt").unwrap_or_else(|error| {
        panic!("Problem creating the file: {:?}", error);
        })
        } else {
        panic!("Problem opening the file: {:?}", error);
        }
        });
        }
      3. Shortcuts for panic on error:unwrap and expect

        1. unwrap is a shortcut method that is implemented just like the matchexpression.if the Result value is Ok variant,unwrap will return the value inside the Ok.If the Result is the Err variant,unwrap will call the panic!macro for us.

          let f = File::open("hello.txt").unwrap()

        2. We use expect in the same way as unwrap: to return the file handle or call the panic! macro. The error message used by expect in its call to panic! will be the parameter that we pass to expect, rather than the default panic! message that unwrap uses.

          let f = File::open("hello.txt").expect("Failed to open")

      4. Propagating Errors

        A shortcut for prapagating errors:? operator

        1
        2
        3
        4
        5
        6
        fn read_username_from_file() -> Result<String, io::Error> {
        let mut f = File::open("hello.txt")?;
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        Ok(s)
        }

        If the value of the Result is an OK,tge value insideOk will get returned from this expression, and the program will continue.if the value is an Err, the Err will be returned from the whole function as if we had used return keyword so the error value gets propagated to the calling code.

        The ? operator can be used in functions that have type of Result.

    3. To panic! Or Not To panic!

      So how do you decide when you should call panic! and when you should return Result? When code panics, there’s no way to recover. You could call panic! for any error situation, whether there’s a possible way to recover or not, but then you’re making the decision on behalf of the code calling your code that a situation is unrecoverable. When you choose to return a Result value, you give the calling code options rather than making the decision for it. The calling code could choose to attempt to recover in a way that’s appropriate for its situation, or it could decide that an Err value in this case is unrecoverable, so it can call panic! and turn your recoverable error into an unrecoverable one. Therefore, returning Result is a good default choice when you’re defining a function that might fail.

  9. Generic Types, Traits, and LifeTImes

    1. Generic Data Types

      1. In Function Definitions

        Before we use a para,eter in the body of the function, we have to declare the parameter name in the signature so the compiller knows what that name means.

        fn largest<T>(list: &[T]) -> T {...}

      2. In Struct Definitions

        1
        2
        3
        4
        struct Point<T> {
        X: T,
        y: T,
        }
        1
        2
        3
        4
        struct Point<T, U> {
        x: T,
        y: U,
        }
      3. In Enum Definitions

        1
        2
        3
        4
        enum Option<T> {
        Some(T),
        None,
        }
        1
        2
        3
        4
        enum Result<T, E> {
        Ok(T),
        Err(E),
        }
      4. In Method Definitions

        1
        2
        3
        4
        5
        6
        7
        8
        9
        struct Point<T> {
        x: T,
        y: T,
        }
        impl<T> Point<T> {
        fn x(&self) -> &T {
        &self.x
        }
        }
    2. Traits:Definning Shared Behavior

      1. A trait tells the Rust compiler about functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior.

      2. Defining a trait

        1
        2
        3
        pub trait Summary {
        fn summarize(&self) -> String;
        }
      3. Imlementing a trait on a type

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        pub struct NewsArticle 
        pub headline: String,
        pub location: String,
        pub author: String,
        pub content: String,
        }

        impl Summary for NewsArticle {
        fn summarize(&self) -> String {
        ......
        }
        }
      4. Default Implementations

        traits can have default implementations

      5. Traits as parameters

      6. We have implemented the Summary trait on the NewsArticle and Tweet types.We can define a notify function that calls the summarize method on its item parameter,which is of some type that implements the Summary trait.

        1
        2
        3
        pub fn notify(item : &impl Summary) {
        println!("Breaking news! {}", item.summarize());
        }
      7. Trait Bound Syntax

        Code in item6 is straightforward but it is actually a syntax sugar for a longer form called trait bound.

        1
        2
        3
        pub fn notify<T : Summary>(item : &T) {
        println!("Breaking news! {}", item.summarize());
        }
      8. Specifying Multiple Trait Bounds with + Syntax

        1
        2
        3
        pub fn notify(item :&(impl Summary + Display)) {
        .....
        }
        1
        2
        3
        pub fn notify<T : Summary + Display> (item: &T) {
        ......
        }
      9. Clearer Trait bounds with where clauses

        1
        2
        3
        4
        fn some_function<T, U>(t: &T, u: &U) -> i32
        where T: Display + Clone,
        U: Clone + Debug
        {
      10. Returning types that implement traits

        1
        fn returns_summarizable() -> impl Summary {...}

        Attention, you can only use impl Trait if you‘re returning a sigle type.

      11. Using Trait Bounds to Conditionally Implement Methods

        We can also conditinally implement a trait for any type that implements another trait.Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations.

    3. Validating references with lifetimes

      1. Preventing dangling references with lifetimes

      2. The Borrow checker

      3. Generic Lifetimes in functions

      4. Lifetime Annotation Syntax

      5. Lifetime Annotations in function signatures

        1
        2
        3
        4
        5
        6
        7
        for longest<’a>(x: &'a str,y: &'a str) -> &'a str{
        if x,len() > y.len() {
        x
        } else {
        y
        }
        }

        The function signature now tells Rust that for some lifetime 'a, the function takes two parameters, both of which are string slices that live at least as long as lifetime 'a. The function signature also tells Rust that the string slice returned from the function will live at least as long as lifetime 'a. In practice, it means that the lifetime of the reference returned by the longest function is the same as the smaller of the lifetimes of the references passed in. These constraints are what we want Rust to enforce. Remember, when we specify the lifetime parameters in this function signature, we’re not changing the lifetimes of any values passed in or returned. Rather, we’re specifying that the borrow checker should reject any values that don’t adhere to these constraints. Note that the longestfunction doesn’t need to know exactly how long x and y will live, only that some scope can be substituted for 'a that will satisfy this signature.

      6. Thinking in terms of lifetimes

      7. Lifetime annotations in struct dedinitions

        We have only defined structs to hold owned types.It is possible for structs to hold references, but in that case we would need to add a lifetime annotation on every reference in the struct’s definition.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        struct ImportantExcerpt<'a> {
        part: &'a str,
        }

        fn main() {
        let novel = String::from("Call me Ishmael. Some years ago...");
        let first_sentence = novel.split('.').next().expect("Could not find a '.'");
        let i = ImportantExcerpt {
        part: first_sentence,
        };
        }

        This annotation means an instance of ImportantExcerpt can not outlive the reference it holds in its part field.

      8. Lifetime Elision

        Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.

        Three rules for lifetime:

        1. Each parameter that is a reference gets its own lifetime parameter
        2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters
        3. There are multiple input lifetime parameters, but one of them is &selft or &mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters.
      9. Lifetime Annotations in Method Definitions

        Lifetime names for struct fields always need to be declared after the impl keyword and then used after the struct’s name, because those lifetimes are part of the struct’s type

        1
        2
        3
        4
        5
        impl<'a> ImportantExcerpt<'a> {
        fn level(&self) -> i32 {
        3
        }
        }
      10. The static lifetime

        Static reference can live for the entire duration of the program

        let s: &'static str = "I have a static lifetime"'

      11. Generic Type parameters, Trait Bounds, and lifetimes Together

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        fn main() {
        let string1 = String::from("abcd");
        let string2 = "xyz";

        let result = longest_with_an_announcement(
        string1.as_str(),
        string2,
        "Today is someone's birthday!",
        );
        println!("The longest string is {}", result);
        }

        use std::fmt::Display;

        fn longest_with_an_announcement<'a, T>(
        x: &'a str,
        y: &'a str,
        ann: T,
        ) -> &'a str
        where
        T: Display,
        {
        println!("Announcement! {}", ann);
        if x.len() > y.len() {
        x
        } else {
        y
        }
        }
  10. Writing Automated Tests

    1. How to Write Tests?

      1. Checking resutls with the assert! Macro

        We give the !assert Marco an argument that avaluates to a Boolean,if the value is true, assert! does nothing an the test passes,if the value is false, the assert! Macro calls the panic! macro.

      2. Testing equality with the assert_eq! And assert_ne! marcos

      3. Adding custom failure messages

      4. Checking for panics with should_panic

        To our test function, this attribute makes a test pass if the code inside the function panics, thet test will fial if the code inside the function doesn’t panic.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        pub struct Guess {
        value: i32,
        }

        impl Guess {
        pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
        panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess { value }
        }
        }

        #[cfg(test)]
        mod tests {
        use super::*;

        #[test]
        #[should_panic]
        fn greater_than_100() {
        Guess::new(200);
        }
        }
    2. Test orgnization

      The #[cfg(test)] annotation on the tests module tells Rust to compile and run the test code only when you run cargo test, not when you run cargo build.

  11. Function Language features:iterators and closures

    1. Closures:aninymous functions that can capture their environment

      1. To define a closure, we start with a pair of vertical pipes|,after the parameters,we place curly brackets that hold the body of the closure,which is optinal if the closure body is a sigle expression.

      2. Closures don’t require you to annotate the types of the parameters or the return value like functions do.The compiler is reliably able to infer the types if the paramaters and the return value, similar to how it’s able to infer types of most variables.

        1
        2
        3
        4
        fn  add_one_v1   (x: u32) -> u32 { x + 1 }
        let add_one_v2 = |x: u32| -> u32 { x + 1 };
        let add_one_v3 = |x| { x + 1 };
        let add_one_v4 = |x| x + 1 ;
      3. Once user calling a closure, compiler refers the type of closure paramaters, and types are then locked into the closure,and we get a type error if we try to use a difference type with the same closure.

      4. Cloures have an additional capability that functions don’t have: they can capture their environment and access variables from the scope in which they’re defined.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        fn main() {
        let x = 4;

        fn equal_to_x(z: i32) -> bool {
        z == x
        }

        let y = 4;

        assert!(equal_to_x(y));
        }
      5. Closures can capture value from their environment in three ways,which directly map to the three ways a function can take a parameter:taking ownership, borrowing mutably, and borrowing immutably.These are encoded in three Fn traits as follows:

        • FnOnce:consumes the variables it captures from its enclosing scope, known as the closure’s environment.To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined.The once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.

        • FnMut:can change the environment because it mutably borrows values.

        • Fn:borrows values from the environment immutably

        When you create a closure, Rust infers which trait to use based on how the closure uses the values from the environment. All closures implement FnOnce because they can all be called at least once. Closures that don’t move the captured variables also implement FnMut, and closures that don’t need mutable access to the captured variables also implement Fn. In Listing 13-12, the equal_to_xclosure borrows x immutably (so equal_to_x has the Fn trait) because the body of the closure only needs to read the value in x.

        If you want to force the closure to take ownership of the values it uses in the environment, you can use the move keyword before the parameter list. This technique is mostly useful when passing a closure to a new thread to move the data so it’s owned by the new thread.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        fn main() {
        let x = vec![1, 2, 3];

        let equal_to_x = move |z| z == x;

        println!("can't use x here: {:?}", x);
        // move this line, the code can be compiled

        let y = vec![1, 2, 3];

        assert!(equal_to_x(y));
        }
    2. Processing a series of items with iterators

      1. Iterators are lazy

      2. The Iterator trait an next method

        1
        2
        3
        4
        5
        6
        7
        pub trait Iterator {
        type Item;

        fn next(&mut self) -> Option<Self::Item>;

        // methods with default implementations elided
        }

        The valuies we get from the calls to next are immutable references to the values in the vector.The iter method produces an iterator over immutable references.If we want to create an iterator that takes ownership of vector and returns owned values, we can call into_iter instead of iter.Similarly, if we want to iterate over mutable references, we can call iter_mut instead of iter.

      3. Methods that consume the iterator

        1
        2
        3
        4
        5
        fn iterator_sum() {
        let v1 = vec![1,2,3];
        let v1_iter = v1.iter();
        let total:i32 = v1.iter.sum();
        }
      4. Methods that produce other iterators

        1
        2
        let v1: Vec<i32> = vec![1,2,3]
        let v2: Vec<_> = v1.iter().map(|x| x+1).collect();

        We use collect method to consume the iterator because iterators are lazy, we collect the results of iterating over the iterator that’s returned from the call to map into a vector.

      5. Using closures that capture their environment

        filter iterator adaptor:The filter method on an iterator takes a closure that takes each item from the iterator and returns a boolean.If the closure returns true, the value will be included in the iterator produced by filter, if the closure returns false, the value won’t be included in the resulting iterator.

        shoes.into_iter().filter(|s| s.size == shoe_size).collect()

      6. Comparing performance:Loops and iterators

        Closures and iterators are Rust features inspired by functional programming language ideas. They contribute to Rust’s capability to clearly express high-level ideas at low-level performance. The implementations of closures and iterators are such that runtime performance is not affected. This is part of Rust’s goal to strive to provide zero-cost abstractions.

  12. More about Cargo and Crates.io (Not covered)

  13. Smart Pointers

    1. In Rust, which uses the concept of ownership and borrowing, an additional difference between references and smart pointers is that the reference are pointers that only borrow data; in contrast, in many cases, smart pointers own the data they point to.

    2. Using Box to point to data on the heap

      Boxes allow you to store data on the heap rather than the stack, what remains on the stack is the pointer to the heap data.

      1. Enabling recursive types with boxes
    3. Treating smart pointers like regular references with the Deref trait

      Implementing the Deref trait allows you to customize the behavior of the dereference operator, *(as opposed to the multiplication or glob operator). By implementing Deref in such a way that a smart pointer can be treated like a regular reference, you can write code that operates on references and use that code with smart pointers too.

      Without the Deref trait, the compiler can only dereference & references.The deref method gives the compiller the ability to take value of any type that implements Deref and call the deref method to get a & reference that it knows how to dereference.

    4. Implicit deref coercions with functions and methods

      Deref coercion is a convenience that Rust performs on arguments to functions and methods. Deref coercion works only on types that implement the Deref trait. Deref coercion converts such a type into a reference to another type. For example, deref coercion can convert &String to &str because String implements the Deref trait such that it returns &str. Deref coercion happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition. A sequence of calls to the deref method converts the type we provided into the type the parameter needs.

      Deref coercion was added to Rust so that programmers writing function and method calls don’t need to add as many explicit references and dereferences with & and *. The deref coercion feature also lets us write more code that can work for either references or smart pointers.

      1
      2
      3
      fn hello(name: &str) {
      println!("Hello, {}!", name);
      }
      1
      2
      3
      4
      fn main() {
      let m = MyBox::new(String::from("Rust"));
      hello(&m);
      }

      with rust deref coercions, the code is equal to

      1
      2
      3
      4
      fn main() {
      let m = MyBox::new(String::from("Rust"));
      hello(&(*m)[..]);
      }
    5. How deref coercion interacts with mutability

      rust does deref coercion when it finds types and trait implementions in three cases:

      • From &T to &U when T: Deref<Target=U>
      • From &mut T to &mut U when T: DerefMut<Target=U>
      • From &mut T to &U when T: Deref<Target=U>
    6. Running code on cleanup with the drop trait

      The second trait important to the smart pointer pattern is Drop, which lets you customize what happens when a value is about to go out of scope. You can provide an implementation for the Droptrait on any type, and the code you specify can be used to release resources like files or network connections. We’re introducing Drop in the context of smart pointers because the functionality of the Drop trait is almost always used when implementing a smart pointer. For example, when a Box<T> is dropped it will deallocate the space on the heap that the box points to.

      Programmer do not have to free memory in code when finishing using an instance of a data type.In rust, when a value goes out of scope,the compiler will insert ‘free memory’ code automatically.

      Most of time, programmers do not have to drop memory of one instance before the scope ending, if have to, rust provides a method std::mem::drop to do this.

    7. Rc, the reference counted smart pointer

      To enable multiple ownership, Rust has a type called Rc<T>, which is an abbreviation for reference counting. The Rc<T> type keeps track of the number of references to a value to determine whether or not the value is still in use. If there are zero references to a value, the value can be cleaned up without any references becoming invalid.

      We use the Rc<T> type when we want to allocate some data on the heap for multiple parts of our program to read and we can’t determine at compile time which part will finish using the data last. If we knew which part would finish last, we could just make that part the data’s owner, and the normal ownership rules enforced at compile time would take effect.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      enum List {
      Cons(i32, Rc<List>),
      Nil,
      }

      use crate::List::{Cons, Nil};
      use std::rc::Rc;

      fn main() {
      let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
      let b = Cons(3, Rc::clone(&a));
      let c = Cons(4, Rc::clone(&a));
      }
    8. RefCell and the interior mutability pattern

      ……..(TODO)

      reference:https://doc.rust-lang.org/book/ch15-05-interior-mutability.html