Golang中如何检查泛型类型的接口实现

Golang中如何检查泛型类型的接口实现 我有一个函数需要比较两个泛型类型。如果它们实现了 Equal 方法,则应优先使用该方法。以下是我的代码:

type EqualChecker interface {
	Equal(EqualChecker) bool
}

//AssertEq adds error to test with stack trace when provided arguments are not equal
//If values of Pointers should be compared use AssertEqPtr
func AssertEq[C comparable](left C, right C, t *testing.T) {
	leftE, ok :=  left.(EqualChecker)
	if ok {
		rightE, _ := right.(EqualChecker)
		if !leftE.Equal(rightE) {
			t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
		}
		return
	}
	if left != right {
		t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
	}
}

但我遇到了编译错误:

  • invalid operation: cannot use type assertion on type parameter value left (variable of type C constrained by comparable)

请问有什么办法可以解决这个问题吗?


更多关于Golang中如何检查泛型类型的接口实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

AssertEq 的唯一要求是两个参数都实现 EqualChecker 接口。

无需泛型。

(见下文)

更多关于Golang中如何检查泛型类型的接口实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我对泛型一无所知,但类型断言仅在表达式为接口类型时才有效。例如,你不能这样做:

var a string = "test"
b := a.(interface{})

因为 a 是一个具体的 string 类型。也许问题在于类型约束中没有确保 C 是一个接口类型。再次声明,我对泛型一无所知,所以我不确定如何修复它。是否有约束要求类型必须是接口类型?

确实,我漏掉了那部分。

我短暂地考虑过使用联合类型,就像这样:

func AssertEq[T Equaler[T] | comparable](left, right T, t *testing.T) {

然而,声明了方法的接口不能作为联合类型的一部分。所以这行不通。

如果没有其他解决方案出现,我想你可能需要将反射作为最后的手段。(我想到了 reflect.DeepEqual。)

感谢您的详细回复,但遗憾的是,您的解决方案没有抓住我的核心要点。

我明确希望接受任何可比较的类型。如果该类型还实现了 Equal 方法,我希望能优先使用该方法。

例如:

  • 我希望能够直接传递两个 int 类型,并使用 ==!= 来比较它们。
  • 如果我传递一个 Time 类型,我希望检查日期是否相等,而不考虑时区。因此,我需要在那里使用 .Equal 而不是 ==!=

如果我想将函数的使用限制在仅实现了 Equal 的类型上,您的解决方案是可行的。

如果类型限制不那么严格,您可能会问在这个例子中使用泛型类型的目的是什么。原因是,只有当传递的两个参数具有相同类型并且能以某种方式进行比较时,这个函数才有意义。

好的,我之前的回答过于仓促,未经深思熟虑就发布了。请忽略它。

这里有一个更好的解决方案,它 (a) 有效,并且 (b) 确实利用了类型参数(也称为泛型)。

Playground

代码解释如下:

  1. 创建一个参数化接口,这样我们就可以定义一个 Equal(T) 函数,而无需命名其参数。
type Equaler[T any] interface {
	Equal(T) bool
}
  1. 函数 AssertEq 被参数化为 AssertEq[T Equaler[T]],因为:
    1. 类型约束是接口,因此我们可以在这里使用 Equaler,并且
    2. Equaler 接口本身是一个参数化接口,因此我们在这里使用 Equaler[T]
func AssertEq[T Equaler[T]](left, right T, t *testing.T) {
	if !left.Equal(right) {
		t.Errorf("Not equal:\nleft: %+v\nright: %+v\n", left, right)
	}
	return
}
  1. 示例结构体类型 AmIEqual 通过定义一个 Equal() 方法来实现接口 Equaler[T]
type AmIEqual struct {
	value int
}

func (e AmIEqual) Equal(other AmIEqual) bool {
	return e.value == other.value
}
  1. 现在我们可以实例化 AmIEqual 值并将其传递给 AssertEq()。 得益于类型推断,无需像 AssertEq[AmIEqual](a, b, t) 这样调用 AssertEq,尽管后者也同样有效。
func TestAssert(t *testing.T) {
   a := AmIEqual{value: 1}
   b := AmIEqual{value: 1}
   AssertEq[AmIEqual](a, b, t) // PASS

   // 注释掉下面两行以获得 PASS 结果
   c := AmIEqual{value: 2}
   AssertEq(a, c, t) // FAIL
}

在Go中,类型参数不能直接使用类型断言。你需要使用类型约束来检查泛型类型是否实现了特定接口。以下是修改后的代码:

import (
    "runtime/debug"
    "testing"
)

type EqualChecker interface {
    Equal(EqualChecker) bool
}

// EqualComparable 约束类型必须实现 EqualChecker 接口或是 comparable 类型
type EqualComparable interface {
    EqualChecker | comparable
}

// AssertEq 比较两个泛型类型的值
func AssertEq[T EqualComparable](left, right T, t *testing.T) {
    // 尝试将 left 转换为 EqualChecker 接口
    if eq, ok := any(left).(EqualChecker); ok {
        // 如果 left 实现了 EqualChecker,那么 right 也应该实现
        if eq2, ok2 := any(right).(EqualChecker); ok2 {
            if !eq.Equal(eq2) {
                t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
            }
            return
        }
    }
    
    // 回退到 comparable 比较
    if left != right {
        t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
    }
}

或者,使用更灵活的类型约束方法:

import (
    "runtime/debug"
    "testing"
)

type EqualChecker interface {
    Equal(EqualChecker) bool
}

// EqualComparable 使用类型集约束
type EqualComparable interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    ~float32 | ~float64 |
    ~string |
    ~bool |
    ~complex64 | ~complex128 |
    EqualChecker
}

// AssertEq 比较两个泛型类型的值
func AssertEq[T EqualComparable](left, right T, t *testing.T) {
    // 通过 any 类型转换进行类型断言
    if eq, ok := interface{}(left).(EqualChecker); ok {
        if eq2, ok2 := interface{}(right).(EqualChecker); ok2 {
            if !eq.Equal(eq2) {
                t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
            }
            return
        }
    }
    
    // 回退到 comparable 比较
    if left != right {
        t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
    }
}

示例类型实现:

type Point struct {
    X, Y int
}

func (p Point) Equal(other EqualChecker) bool {
    if o, ok := other.(Point); ok {
        return p.X == o.X && p.Y == o.Y
    }
    return false
}

// 使用示例
func TestAssertEq(t *testing.T) {
    p1 := Point{X: 1, Y: 2}
    p2 := Point{X: 1, Y: 2}
    p3 := Point{X: 1, Y: 3}
    
    AssertEq(p1, p2, t) // 通过
    AssertEq(p1, p3, t) // 失败
    
    // 也可以用于基本类型
    AssertEq(1, 1, t)   // 通过
    AssertEq(1, 2, t)   // 失败
}

关键点:

  1. 使用 any(left)interface{}(left) 将泛型类型转换为接口类型
  2. 定义合适的类型约束来限制泛型类型范围
  3. 类型断言只能在接口类型上使用,不能直接在类型参数上使用
回到顶部