将对象或指针作为接口参数传递时,避免类型错误。

huangapple go评论82阅读模式
英文:

Avoiding type errors when passing objects or pointers as interface parameters

问题

我最近在我的代码中追踪到一个错误,我对于为什么Go语言没有保护我免受这个错误的困惑。代码大致如下(下面是一个最小化、完整、可复现的示例):

// 事件的通用接口
type Event interface{}

// 具体的事件类型
type ConcreteEvent struct{}

func EventHandler(e Event) {
    // 检查事件是否具有特定类型,并进行不同的处理
    c, ok := e.(ConcreteEvent)
    if ok {
        // 处理 ConcreteEvent 类型的事件
        fmt.Println("处理 ConcreteEvent")
    } else {
        // 处理其他类型的事件
        fmt.Println("处理其他类型的事件")
    }
}

ce := ConcreteEvent{}

// 两次调用事件处理函数,Go语言都接受
EventHandler(ce)
EventHandler(&ce)

奇怪的是,这两次调用都被接受了。就我所知,它们的含义没有任何疑问,所以为什么不都接受呢?然而,我的主要关注点是,第二次调用不会进入EventHandler()函数内的if分支,而是进入else分支。如果我将检查语句改为c, ok := e.(*ConcreteEvent),这两次调用的行为就会互换。

我的直觉是这是Go语言设计上的一个小故障或错误。然而,更改它将改变现有代码的行为,这在版本1中很难或不可能修复。然而,这并不是我的主要关注点,我的主要关注点是找到这些情况并避免这个错误。那么,有没有任何方法(或编码策略)可以避免这个问题,或者至少让我意识到它?

最小化、完整、可复现的示例(MCVE)

package main

import "fmt"

// 事件的通用接口
type Event interface {
    Value() int
}

// 具体的事件类型
type ConcreteEvent struct {
    val int
}

func (e ConcreteEvent) Value() int {
    return e.val
}

func EventHandler(e Event) {
    // 检查事件是否具有特定类型,并进行不同的处理
    _, ok := e.(ConcreteEvent)
    if ok {
        fmt.Println("ConcreteEvent")
    } else {
        fmt.Println("general Event")
    }
}

func main() {
    ce := ConcreteEvent{
        val: 42,
    }

    // 两次调用事件处理函数,Go语言都接受
    EventHandler(ce)
    EventHandler(&ce)
}

输出结果

ConcreteEvent
general Event
英文:

I recently tracked down a bug in my code where I'm basically confused why Go doesn't protect me from it. The code is roughly this (MCVE below):

// common interface for events
interface Event ...

// concrete event type
type ConcreteEvent ...

func EventHandler(e Event) {
    // check if the event has a specific type and treat that differently
    c, ok := e.(ConcreteEvent)
    if ok {
        ...
    } else {
        ...
    }
}

ce := ConcreteEvent{...}

// two calls to the event handler that are both accepted by Go
EventHandler(ce)
EventHandler(&ce)

The first thing that is weird is that both calls are accepted. Ok, as far as I'm concerned, there is no doubt about the meaning, so why not accept both. However, and that is my main concern, the second one will not enter the if branch inside EventHandler() but the else branch. The behaviour switches between the calls if I change the check to c, ok := e.(*ConcreteEvent).

My gut feeling is that this is a glitch or bug in the design of Go. However, changing it would change behaviour of existing code, which makes it difficult or impossible to fix in version 1. However, that's not my main concern, which is rather to find these cases and to avoid this error. So, are there any ways (or coding strategies) for avoiding this or at least becoming aware of it?

MCVE

Code

package main

import "fmt"

// common interface for events
type Event interface {
    Value() int
}

// concrete event type
type ConcreteEvent struct {
    val int
}

func (e ConcreteEvent) Value() int {
    return e.val
}

func EventHandler(e Event) {
    // check if the event has a specific type and treat that differently
    _, ok := e.(ConcreteEvent)
    if ok {
        fmt.Println("ConcreteEvent")
    } else {
        fmt.Println("general Event")
    }
}


func main() {
    ce := ConcreteEvent{
        val: 42,
    }

    // two calls to the event handler that are both accepted by Go
    EventHandler(ce)
    EventHandler(&ce)
}

Output

ConcreteEvent
general Event

答案1

得分: 4

指针类型*T的方法集包括在值接收器上声明的方法:

规范,方法集

相应指针类型 *T 的方法集是所有使用接收器 *T 或 T 声明的方法的集合(也就是说,它还包含 T 的方法集)

因此,ConcreteEvent*ConcreteEvent都实现了Event接口。

这并不是语言中的“bug”。实际上,它已经被正确记录。为了避免这个问题,你可以选择:

  • 使用指针接收器声明方法,这样只有指针类型才会实现接口

  • 或者如果两种类型始终有效,你可以使用双重 case 进行类型切换:

	switch t := e.(type) {
	case ConcreteEvent, *ConcreteEvent:
		fmt.Println("ConcreteEvent", t.Value())
	}

请注意,在双重 case 中,t 的类型是 Event

英文:

The method set of a pointer type *T includes the methods declared on the value receiver:

Specs, Method sets

> The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T)

So both ConcreteEvent and *ConcreteEvent implement the Event interface.

This is not a "bug" in the language. In fact, it's properly documented. To avoid this issue, you either:

  • declare the method with the pointer receiver, so that only pointer types will implement the interface

  • or if both types are always valid, you can type-switch with the double case:

	switch t := e.(type) {
	case ConcreteEvent, *ConcreteEvent:
		fmt.Println("ConcreteEvent", t.Value())
	}

Note that in the double case, the type of t is Event.

答案2

得分: -1

没有。这不是一个问题。

英文:

> So, are there any ways (or coding strategies) for avoiding this or at least becoming aware of it?

No. This is a non problem.

huangapple
  • 本文由 发表于 2021年11月28日 00:29:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/70136671.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定