golang函数接收器及安全问题 2022-10-20

2022-10-19  本文已影响0人  9_SooHyun

不管接收者类型是值类型还是指针类型,都可以通过值类型或指针类型调用

package main

import "fmt"

type Person struct {
    age int
}

func (p Person) howOld() int {
    return p.age
}

func (p *Person) growUp() {
    p.age += 1
}

func main() {
    // qcrao 是值类型
    qcrao := Person{age: 18}

    // 值类型 调用 接收者也是值类型 的方法
    fmt.Println(qcrao.howOld()) // 18

    // 值类型 调用 接收者是指针类型 的方法
    qcrao.growUp()
    fmt.Println(qcrao.howOld()) // 19

    // ----------------------

    // stefno 是指针类型
    stefno := &Person{age: 100}

    // 指针类型 调用接收者是值类型的方法
    fmt.Println(stefno.howOld()) // 100

    // 指针类型 调用接收者也是指针类型的方法
    stefno.growUp()
    fmt.Println(stefno.howOld()) // 101
}

实际上,当类型和方法的接收者类型不同时,其实是编译器在背后做了一些工作:

值接收者 指针接收者
值类型调用者 方法会使用调用者的一个副本,类似于“传值” 使用值的引用来调用方法,上例中,qcrao.growUp() 实际上是 (&qcrao).growUp()
指针类型调用者 指针被解引用为值,上例中,stefno.howOld() 实际上是 (*stefno).howOld() 实际上也是“传值”,方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针

因此,不管接收者类型是值类型还是指针类型,都可以通过值类型或指针类型调用,这里面实际上通过语法糖起作用的
但需要注意的是,虽然go允许解引或者取引来实现调用,我们仍然需要关注里面的安全问题:

go interface 对 receiver 的安全限制

go的interface就进行了严格的安全限制,不允许接口提供的方法对接口本身产生任何可能的破坏
就像我们在使用公牛插座的时候,插座本身需要保证插座提供的功能不会破坏插座本身
因此,以下的接口实现是非法的

package main

import "fmt"

type coder interface {
    code()
    debug()
}

type Gopher struct {
    language string
}

func (p Gopher) code() {
    fmt.Printf("I am coding %s language\n", p.language)
}

func (p *Gopher) debug() {
    fmt.Printf("I am debuging %s language\n", p.language)
}

func main() {
    var c coder = Gopher{"Go"} // var c coder = Gopher{"Go"} will err: Gopher does not implement coder (debug method has pointer receiver). this happens because a value calling a pointer receiver's function is unsafe
    c.code()
    c.debug() // c被取引用以调用debug(),是不安全的
}

var c coder = &Gopher{"Go"}则可以正常实现coder接口,因为它调用任何接口方法都是安全的

package main

import "fmt"

type coder interface {
    code()
    debug()
}

type Gopher struct {
    language string
}

func (p Gopher) code() {
    fmt.Printf("I am coding %s language\n", p.language)
}

func (p *Gopher) debug() {
    fmt.Printf("I am debuging %s language\n", p.language)
}

func main() {
    var c coder = &Gopher{"Go"} 
    c.code() // c被解引用以调用code(),是安全的
    c.debug()
}

抛弃了接口后的以下常规代码,Gopher可以正常调用*Gopher.debug,这是因为上面提到的,编译器自动对c做了引用。常规代码的安全,go交给了开发者

package main

import "fmt"

type Gopher struct {
    language string
}

func (p Gopher) code() {
    fmt.Printf("I am coding %s language\n", p.language)
}

func (p *Gopher) debug() {
    p.language = "go1.18"
    fmt.Printf("I am debuging %s language\n", p.language)
}

func main() {
    var c = Gopher{"Go"}
    c.code()  // I am coding Go language
    c.debug() // I am debuging go1.18 language
    
    // 值调用指针接收器方法,产生安全问题,因为值被取了指针,接收器获取了该指针,对原对象造成修改
    fmt.Println("final language: ", c.language) // final language:  go1.18, which means c has been changed.
    
}

小结

从语法角度讲,不管接收者类型是值类型还是指针类型,都可以通过值类型或指针类型调用

而实际上,我们在编程实践中,应该遵循interface一样的规范:
避免隐式地取引调用,保证安全

上一篇下一篇

猜你喜欢

热点阅读