TL在Go如何实现oneof语义

TL的Type是一个总类型Class包含不同的结构子类型实例Object,类似于Protubuf中MessageOneof的关系。

Telegram的RPC TL片段1:

peerUser#9db1bc6d user_id:int = Peer;
peerChat#bad0e5bb chat_id:int = Peer;
peerChannel#bddde532 channel_id:int = Peer;

folderPeer#e9baa668 peer:Peer folder_id:int = FolderPeer;

上述TL片段1 等价的Protobuf描述:

syntax = "proto3";

package tg_oneof;

option go_package = "./tg_oneof";


message Peer {
	message PeerUser {
		int32 user_id = 1;  
	}
	message PeerChat {
		int32 chat_id = 1;
	}
	message PeerChannel {
	    int32 channel_id = 1;
	}
  
  oneof oneofPeer {
    PeerUser peerUser = 1;
    PeerChat peerChat = 2;
    PeerChannel peerChannel = 3;
  }
}

message FolderPeer {
	message FolderPeer {
		oneof oneofPeer {
			Peer.PeerUser peerUser = 1;
			Peer.PeerChat peerChat = 2;
			Peer.PeerChannel peerChannel = 3;
		}
		int32 folder_id = 1;
	}
}

gotd生成的代码:

// PeerClass represents Peer generic type.
//
// Constructors:
// - [PeerUser]
// - [PeerChat]
// - [PeerChannel]
//
// Example:
//
// g, err := tg.DecodePeer(buf)
// if err != nil {
//     panic(err)
// }
// switch v := g.(type) {
// case *tg.PeerUser: // peerUser#59511722
// case *tg.PeerChat: // peerChat#36c6019a
// case *tg.PeerChannel: // peerChannel#a2a5371e
// default: panic(v)
// }
type PeerClass interface {
    bin.Encoder
    bin.Decoder
    bin.BareEncoder
    bin.BareDecoder
    construct() PeerClass

    // TypeID returns type id in TL schema.
    TypeID() uint32
    // TypeName returns name of type in TL schema.
    TypeName() string
    // String implements fmt.Stringer.
    String() string
    // Zero returns true if current object has a zero value.
    Zero() bool
}

// PeerChannel represents TL type `peerChannel#a2a5371e`.
// Channel/supergroup
type PeerChannel struct {
    // Channel ID
    ChannelID int64
}

// construct implements constructor of PeerClass.
func (p PeerChannel) construct() PeerClass { return &p }

// PeerChat represents TL type `peerChat#36c6019a`.
// Group.
type PeerChat struct {
	// Group identifier
	ChatID int64
}

// construct implements constructor of PeerClass.
func (p PeerChat) construct() PeerClass { return &p }

// PeerUser represents TL type `peerUser#59511722`.
// Chat partner
type PeerUser struct {
	// User identifier
	UserID int64
}

// construct implements constructor of PeerClass.
func (p PeerUser) construct() PeerClass { return &p }

// FolderPeer represents TL type `folderPeer#e9baa668`.
// Peer in a folder
type FolderPeer struct {
	// Folder peer info
	Peer PeerClass
	// Peer folder ID, for more info click here¹
	FolderID int
}

这里的关键是必须有某种方式约束PeerClass只能是PeerUser/PeerChat/PeerChannel之一,gotd巧妙的使用未导出方法 construct() PeerClass 来约束只有与PeerClass同一包内的实现该私有方法的PeerUser/PeerChat/PeerChannel才能赋值给PeerClass变量。

Go中 switch type 断言与Rust的match type有异曲同工之妙:

  • Go代码:
switch v := g.(type) {
case *tg.PeerUser: // peerUser#59511722
case *tg.PeerChat: // peerChat#36c6019a
case *tg.PeerChannel: // peerChannel#a2a5371e
default: panic(v)
}
  • 等价Rust代码:
match g {
	tg::PeerUser(user) => // peerUser#59511722
	tg::PeerChat(chat) => // peerChat#36c6019a
	tg::PeerChannel(channel) => // peerChannel#a2a5371e
	_ => None
}
 在go库包中包含main包的方式 git-sync同步所有git库 

Comments