redigo遇到protobuf枚举

背景

redis中存储着一个hash类型的键:

键名:redis:test:hash

字段名:0

字段值:0

代码中操作redis的函数定义为

Do(cmd string, args ...interface{}) (reply interface{}, err error) {
// TODO
}

proto文件中有如下定义

enum RoomTag
{
    matchType = 0;
}

看一下下面的代码

// rep预期类型是[]uint8, 转换成string是"0",实际结果rep是nil
rep, err := Do("hget", "redis:test:hash", RoomTag_matchType)

// 这样执行rep就符合预期了
rep, err := Do("hget", "redis:test:hash", int32(RoomTag_matchType))

// 这样执行rep也符合预期
rep, err := Do("hget", "redis:test:hash", 0)

很奇怪为什么第一个语句执行的结果不符合预期,本着打破砂锅问到底的精神,好好探询了一番。

知识点

类型别名和类型定义

从这两个概念基本也能看出来区别。类型别名就是一个类型的别名,声明一个类型别名后并没有增加类型,新声明的类型别名和原类型本质上是同一个类型,两者可以直接互相赋值,在type switch里两者也只能共用一个分支;而类型定义就是定义了一个全新的类型,它只是继承了原类型了属性,但是和原类型是完全不同的。两者在代码上的差别就是一个等于号。示例:

type NewInt int    // 我执行了类型定义,NewInt是一个全新的类型
type AliasInt = int    // 我就是声明了一个类型别名,AliasInt和int是同一个类型

var ni NewInt = 1
var nit int = ni    // 错误:不能把'ni'(NewInt类型)当做int类型使用

var ai AliasInt = 1
var ait int = ai    // 正确:我们是同一类型

switch ni.(type) {
case int:    // 正确:int和AliasInt均会走此分支
case NewInt:    // 正确:NewInt会走此分支
case AliasInt:    // 错误:和int是同一类型,不能重复
}

Stringer interface

看一下Stringer的定义

// Stringer is implemented by any value that has a String method,
// which defines the ``native'' format for that value.
// The String method is used to print values passed as an operand
// to any format that accepts a string or to an unformatted printer
// such as Print.
type Stringer interface {
   String() string
}

也就是说,如果一个类型实现了Stringer接口里的String方法,那么它就定义了一个'原生'的格式化方法,当一个该类型的值作为参数传递给一个未指明格式的printer(比如说Print)或者一个接受string的格式化printer时,String方法将会被调用,打印出的值即为该String方法返回的值。

type NewInt int
type NewIntS int32

func (n NewIntS) String() string {
   return fmt.Sprintf("%d - kidding", n + 1)
}


var ni NewInt = 1
fmt.Printf("%s\n", ni)    // %!s(main.NewInt=1)
fmt.Print(ni, "\n")    //  1

var nis NewIntS = 1
fmt.Printf("%s\n", nis)    // 2 - kidding
fmt.Print(nis, "\n")    // 2 - kidding

原因探究

了解了上面的知识点后,前面的问题就好理解了。先看一下相关的源代码。

首先是redigo/redis/conn.go里的相关代码段:

// 这个函数就是负责把用户传过来的interface{}参数写入请求中
func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
	switch arg := arg.(type) {
	case string:
		return c.writeString(arg)
	case []byte:
		return c.writeBytes(arg)
	case int:
		return c.writeInt64(int64(arg))
	case int64:
		return c.writeInt64(arg)
	case float64:
		return c.writeFloat64(arg)
	case bool:
		if arg {
			return c.writeString("1")
		} else {
			return c.writeString("0")
		}
	case nil:
		return c.writeString("")
	case Argument:
		if argumentTypeOK {
			return c.writeArg(arg.RedisArg(), false)
		}
		// See comment in default clause below.
		var buf bytes.Buffer
		fmt.Fprint(&buf, arg)
		return c.writeBytes(buf.Bytes())
	default:
		// This default clause is intended to handle builtin numeric types.
		// The function should return an error for other types, but this is not
		// done for compatibility with previous versions of the package.
		var buf bytes.Buffer
		fmt.Fprint(&buf, arg)
		return c.writeBytes(buf.Bytes())
	}
}

然后再看一下protobuf把proto文件转换成pb文件后,枚举类型的代码

type RoomTag int32

const (
	RoomTag_matchType RoomTag = 0
)

var RoomTag_name = map[int32]string{
	0: "matchType",
}

var RoomTag_value = map[string]int32{
	"matchType": 0,
}

func (x RoomTag) String() string {
	return proto.EnumName(RoomTag_name, int32(x))
}

结合源代码,前面的问题原因就清晰了。RoomTag是一个新的类型定义,并不是int32的别名(PS: 就算是别名,因为switch里没有定义int32,所以最终也是进入default分支里),所以在writeArg方法里进入了default分支,然后default分支里使用了fmt.Fprint(&buf, arg)来赋值,由于RoomTag类型定义了String方法,所以fmt.Fprint使用了RoomTag.String方法的返回值,即"matchType", 所以最终发出去的hget请求等价于

Do("hget", "redis:test:hash", "matchType")

由于这个键中并没有matchType这个字段,所以返回了nil

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值