Go的http.rpc是一个代码量少但设计精妙的标准库,如果我们想深入学习Go的用法以及想深入学习rpc的底层实现,这个是一个很好的学习资源。这里开始从源码来分析go rpc库的核心内容。

一个rpc框架需要解决以下几个核心技术问题:

  1. 跨平台跨语言:数据序列化和反序列化
  2. 怎么确认当前response对应的是哪个rpc request
  3. rpc请求发出后,怎么维护这些等待回复的协程
  4. 异步模式的支持:callback,阻塞,future

接下来,我们将从RPC服务端和客户端的各自实现来分析Go rpc标准库的核心内容。

客户端发起一个远程调用

https://github.com/golang/go/blob/master/src/net/rpc/client_test.go

按照官方例子,这样启动一个rpc客户端并发起一次远程调用

  1. // 跟远程server建立tcp连接
  2. client, err := rpc.Dial("tcp", "127.0.0.1:8080")
  3. // 除了上面的tcp模式,还支持http模式,jsonrpc模式建立连接
  4. // jsonrpc.Dial("tcp", "127.0.0.1:8080");
  5. // rpc.DialHTTP("tcp", "127.0.0.1:8080");
  6. if err != nil {
  7. panic(err)
  8. }
  9. // 发起一个远程调用,方法是S.Recv,请求是struct{}{},回复是reply结构体,这是个阻塞调用
  10. var reply Reply
  11. err = client.Call("S.Recv", &struct{}{}, &reply)
  12. if err != nil {
  13. panic(err)
  14. }
  15. fmt.Printf("%#v\n", reply)

首先我们先来分析一下Client结构体,这个结构体负责管理RPC客户端远程调用的所有信息。

  1. // Client represents an RPC Client.
  2. // There may be multiple outstanding Calls associated
  3. // with a single Client, and a Client may be used by
  4. // multiple goroutines simultaneously.
  5. type Client struct {
  6. codec ClientCodec //客户端的编解码模块
  7. reqMutex sync.Mutex // protects followin,保护request的互斥锁
  8. request Request
  9. mutex sync.Mutex // protects following;seq的互斥锁
  10. seq uint64
  11. pending map[uint64]*Call // 还没请求返回的请求会放在这个map里,即请求处于pending状态
  12. closing bool // user has called Close 这个client是否已经正在关闭
  13. shutdown bool // server has told us to stop ; client关闭标记
  14. }
  15. // Request is a header written before every RPC call. It is used internally
  16. // but documented here as an aid to debugging, such as when analyzing
  17. // network traffic.
  18. type Request struct {
  19. ServiceMethod string // format: "Service.Method"
  20. Seq uint64 // sequence number chosen by client
  21. next *Request // for free list in Server
  22. }

Client结构体是管理发起远程调用的管理者,一个进程中理论上只需new一个client,后续的所有的请求都由这个client管理。这里详细解析client结构体的关键数据结构:

  1. codec ClientCodec:编解码模块,用于请求发出时的编码,收到response时的解码,编码方式支持gob(二进制,效率更高)和json。
  2. request Request:请求结构体
  3. reqMutex sync.Mutex:对request进行加锁,保证并发安全
  4. seq uint64:请求的序列号,标记每个请求。每发起一个rpc请求,这个值就加一
  5. mutex sync.Mutex:seq的互斥锁,因为并发会存在一个时间下多个协程修改seq的情形。
  6. pending map[uint64]*Call:使用一个map来缓存发出了请求但尚未收到回复的所有请求。
  7. closing bool:client即将关闭,处理完剩余请求就会shutdown
  8. shutdown bool: client关闭标记

在client机构体中,我们使用了一个 map[uint64]*Call 来存储发出但没返回的请求,这里的Call结构体如下:

  1. // Call represents an active RPC.
  2. type Call struct {
  3. ServiceMethod string // The name of the service and method to call.
  4. Args interface{} // The argument to the function (*struct).
  5. Reply interface{} // The reply from the function (*struct).
  6. Error error // After completion, the error status.
  7. Done chan *Call // Receives *Call when Go is complete.
  8. }

Dial方法是用于跟目标server建立好rpc连接,传入的参数是网络协议和对方server的IP:PORT。rpc的Dial里面调用的是net.Dial,建立一个tcp连接,同时Dial方法调用了NewClient(conn),并作为返回值,返回上层。

NewClient实际new的是一个gob编解码对象gobClientCodec,在NewClientWithCodec方法里,创建了一个协程(go client.input())专门处理远程调用的response message,当从网络中收到消息时,就会触发input里的后续处理逻辑。

  1. // Dial connects to an RPC server at the specified network address.
  2. func Dial(network, address string) (*Client, error) {
  3. conn, err := net.Dial(network, address)
  4. if err != nil {
  5. return nil, err
  6. }
  7. return NewClient(conn), nil
  8. }
  9. // NewClient returns a new Client to handle requests to the
  10. // set of services at the other end of the connection.
  11. // It adds a buffer to the write side of the connection so
  12. // the header and payload are sent as a unit.
  13. //
  14. // The read and write halves of the connection are serialized independently,
  15. // so no interlocking is required. However each half may be accessed
  16. // concurrently so the implementation of conn should protect against
  17. // concurrent reads or concurrent writes.
  18. func NewClient(conn io.ReadWriteCloser) *Client {
  19. encBuf := bufio.NewWriter(conn)
  20. client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
  21. return NewClientWithCodec(client)
  22. }
  23. // NewClientWithCodec is like NewClient but uses the specified
  24. // codec to encode requests and decode responses.
  25. func NewClientWithCodec(codec ClientCodec) *Client {
  26. client := &Client{
  27. codec: codec,
  28. pending: make(map[uint64]*Call),
  29. }
  30. go client.input()
  31. return client
  32. }

Call方法我认为是整个rpc框架中最重要的一环,这个接口屏蔽了请求从发送到等待,再到收到回复唤醒协程的整个过程。

观察Call的实现,传入参数分别为远程调用的函数方法名,请求参数,回复结构体。注意请求和回复是interface类型,表示我们可以传入任意类型的结构体,十分灵活。

调用Call方法时,我们就会对指定server发起指定函数调用,整个远程调用的流程比较复杂,涉及到编码、网络、远程Server的逻辑处理、client收到回包、解码多个子步骤,但是这里并没有新创建一个协程来维护整个远程调用流程。收到回复的通知是通过channel来实现的。

  1. // Call invokes the named function, waits for it to complete, and returns its error status.
  2. func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {
  3. call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
  4. return call.Error
  5. }

Call方法时对Go方法的封装,核心代码是处于Go函数里,Go方法里实现了以下几个事情:

  1. new一个Call对象
  2. 给Call对象赋值serviceMethod,args,reply,以及参数传入的channel
  3. client.send 发送请求
  1. // Go invokes the function asynchronously. It returns the Call structure representing
  2. // the invocation. The done channel will signal when the call is complete by returning
  3. // the same Call object. If done is nil, Go will allocate a new channel.
  4. // If non-nil, done must be buffered or Go will deliberately crash.
  5. func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
  6. call := new(Call)
  7. call.ServiceMethod = serviceMethod
  8. call.Args = args
  9. call.Reply = reply
  10. if done == nil {
  11. done = make(chan *Call, 10) // buffered.
  12. } else {
  13. // If caller passes done != nil, it must arrange that
  14. // done has enough buffer for the number of simultaneous
  15. // RPCs that will be using that channel. If the channel
  16. // is totally unbuffered, it's best not to run at all.
  17. if cap(done) == 0 {
  18. log.Panic("rpc: done channel is unbuffered")
  19. }
  20. }
  21. call.Done = done
  22. client.send(call)
  23. return call
  24. }

我们继续探索核心函数client.send,client.send负责的工作就是将远程调用的请求发出,幷将请求序列号+1,把等待回复的请求协程放在pending map中缓存。

  1. func (client *Client) send(call *Call) {
  2. client.reqMutex.Lock()
  3. defer client.reqMutex.Unlock()
  4. // Register this call.
  5. client.mutex.Lock() // 保护seq
  6. if client.shutdown || client.closing { // 如果client处于关闭或者正在状态下,我们本次发送是失败的
  7. client.mutex.Unlock()
  8. call.Error = ErrShutdown
  9. call.done()
  10. return
  11. }
  12. seq := client.seq
  13. client.seq++ // 请求序列号自增
  14. client.pending[seq] = call // 请求放在pending map里缓存
  15. client.mutex.Unlock()
  16. // Encode and send the request.
  17. client.request.Seq = seq // 请求的序列号是要带过去server那边,到时请求返回时再带回来,这才能直到这是哪个请求的回复
  18. client.request.ServiceMethod = call.ServiceMethod
  19. err := client.codec.WriteRequest(&client.request, call.Args) // 使用编码器做请求编码,并发送到网络
  20. if err != nil {
  21. client.mutex.Lock()
  22. call = client.pending[seq]
  23. delete(client.pending, seq)
  24. client.mutex.Unlock()
  25. if call != nil {
  26. call.Error = err
  27. call.done()
  28. }
  29. }
  30. }

请求的发送我们已经了解,现在开始分析请求的response是怎么接收,幷唤醒休眠的协程的。这部分逻辑为input()函数,input()函数是在NewClient时就触发执行。

  1. 先通过client编解码器解码出response
  2. 获取到序列号response.Seq,通过该序列号找出pending map中对应的rpc请求,取出请求后就从map中删除该请求。注意这个步骤需要加锁。
  3. 根据response的error信息来决定是走错误处理流程还是走正常数据处理流程
  4. client.codec.ReadResponseBody 将response数据读到call.Reply对象中,幷调用call.done()来通过channel唤醒等待的协程。
  5. 特别注意,如果远程调用出错(调用失败,回复的序列号并不在pending map中),会跳出接收消息的循环,直接走到关闭连接的流程:client.shutdown设置为true,然后遍历pending map,通知里面缓存的所有等待回复的请求协程需要因错误而关闭。
  1. func (client *Client) input() {
  2. var err error
  3. var response Response
  4. for err == nil {
  5. response = Response{}
  6. err = client.codec.ReadResponseHeader(&response)
  7. if err != nil {
  8. break
  9. }
  10. seq := response.Seq
  11. client.mutex.Lock()
  12. call := client.pending[seq]
  13. delete(client.pending, seq)
  14. client.mutex.Unlock()
  15. switch {
  16. case call == nil: // 如果response中带的序列号我们在pending map里找不到,会导致err不为nil,那就直接跳到了switch后的流程,关闭整个rpc连接
  17. // We've got no pending call. That usually means that
  18. // WriteRequest partially failed, and call was already
  19. // removed; response is a server telling us about an
  20. // error reading request body. We should still attempt
  21. // to read error body, but there's no one to give it to.
  22. err = client.codec.ReadResponseBody(nil)
  23. if err != nil {
  24. err = errors.New("reading error body: " + err.Error())
  25. }
  26. case response.Error != "":
  27. // We've got an error response. Give this to the request;
  28. // any subsequent requests will get the ReadResponseBody
  29. // error if there is one.
  30. call.Error = ServerError(response.Error)
  31. err = client.codec.ReadResponseBody(nil)
  32. if err != nil {
  33. err = errors.New("reading error body: " + err.Error())
  34. }
  35. call.done()
  36. default:
  37. err = client.codec.ReadResponseBody(call.Reply)
  38. if err != nil {
  39. call.Error = errors.New("reading body " + err.Error())
  40. }
  41. call.done()
  42. }
  43. }
  44. // Terminate pending calls.
  45. client.reqMutex.Lock()
  46. client.mutex.Lock()
  47. client.shutdown = true
  48. closing := client.closing
  49. if err == io.EOF {
  50. if closing {
  51. err = ErrShutdown
  52. } else {
  53. err = io.ErrUnexpectedEOF
  54. }
  55. }
  56. for _, call := range client.pending {
  57. call.Error = err
  58. call.done()
  59. }
  60. client.mutex.Unlock()
  61. client.reqMutex.Unlock()
  62. if debugLog && err != io.EOF && !closing {
  63. log.Println("rpc: client protocol error:", err)
  64. }
  65. }

当我们显式执行client.close时,实际执行的是:

  1. 加锁,将client.closing设置为true
  2. 解锁,关闭client的解码模块,client.codec.Close(),
  1. // Close calls the underlying codec's Close method. If the connection is already
  2. // shutting down, ErrShutdown is returned.
  3. func (client *Client) Close() error {
  4. client.mutex.Lock()
  5. if client.closing {
  6. client.mutex.Unlock()
  7. return ErrShutdown
  8. }
  9. client.closing = true
  10. client.mutex.Unlock()
  11. return client.codec.Close()
  12. }

以上调用client.call时是同步阻塞的RPC调用,如果我们希望异步调用,可以这么写,利用client.Go来获取channel,在合适时再去监听channel的消息,如果此时消息已经到达,那就取数据继续执行;如果数据未到达那就继续阻塞。这个模式也就是我们常说的future模式。

  1. // Asynchronous call
  2. quotient := new(Quotient)
  3. divCall := client.Go("Arith.Divide", args, quotient, nil)
  4. replyCall := <-divCall.Done // will be equal to divCall

服务器收到一个远程调用

我们以支持http协议的rpc server为例,开始我们的源码分析,下面是启动一个rpc server幷注册服务的流程。这里我们定义了Rect结构体,Rect里实现了Area和Perimeter方法。

  1. type Params struct {
  2. Width, Height int;
  3. }
  4. type Rect struct{}
  5. //函数必须是导出的
  6. //必须有两个导出类型参数
  7. //第一个参数是接收参数
  8. //第二个参数是返回给客户端参数,必须是指针类型
  9. //函数还要有一个返回值error
  10. func (r *Rect) Area(p Params, ret *int) error {
  11. *ret = p.Width * p.Height;
  12. return nil;
  13. }
  14. func (r *Rect) Perimeter(p Params, ret *int) error {
  15. *ret = (p.Width + p.Height) * 2;
  16. return nil;
  17. }
  18. func main() {
  19. rect := new(Rect);
  20. //注册一个rect服务
  21. rpc.Register(rect);
  22. //把服务处理绑定到http协议上
  23. rpc.HandleHTTP();
  24. err := http.ListenAndServe(":8080", nil);
  25. if err != nil {
  26. log.Fatal(err);
  27. }
  28. }

Register实际调用的是Server.register,里面首先利用反射机制获取服务rcvr的type,value,以及服务名(结构体名就是服务名),如果有传入参数name才把服务名改为name,否则默认使用结构体rcvr变量名字。当client发起远程调用server方法时,通过显式指定方法名”Rect.Area”表明调用的是server Rect对象的Area方法。

register使用了go的反射特性,这里回忆一下TypeOf()和ValueOf()的特性。使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。reflect.ValueOf 返回 reflect.Value 类型,包含有 rawValue 的值信息。reflect.Value 与原值间可以通过值包装和值获取互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。

  1. func (server *Server) Register(rcvr interface{}) error {
  2. return server.register(rcvr, "", false)
  3. }
  4. func (server *Server) register(rcvr interface{}, name string, useName bool) error {
  5. s := new(service)
  6. s.typ = reflect.TypeOf(rcvr)
  7. s.rcvr = reflect.ValueOf(rcvr)
  8. sname := reflect.Indirect(s.rcvr).Type().Name()
  9. if useName {
  10. sname = name
  11. }
  12. if sname == "" {
  13. s := "rpc.Register: no service name for type " + s.typ.String()
  14. log.Print(s)
  15. return errors.New(s)
  16. }
  17. if !token.IsExported(sname) && !useName {
  18. s := "rpc.Register: type " + sname + " is not exported"
  19. log.Print(s)
  20. return errors.New(s)
  21. }
  22. s.name = sname
  23. // Install the methods
  24. s.method = suitableMethods(s.typ, logRegisterError)
  25. if len(s.method) == 0 {
  26. str := ""
  27. // To help the user, see if a pointer receiver would work.
  28. method := suitableMethods(reflect.PtrTo(s.typ), false)
  29. if len(method) != 0 {
  30. str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
  31. } else {
  32. str = "rpc.Register: type " + sname + " has no exported methods of suitable type"
  33. }
  34. log.Print(str)
  35. return errors.New(str)
  36. }
  37. if _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
  38. return errors.New("rpc: service already defined: " + sname)
  39. }
  40. return nil
  41. }

suitableMethods方法完成的事情是将对象rcvr实现的所有方法都放进一个map里存储,suitableMethods的返回值就是一个map(map[string]*methodType),key是对象rcvr实现的方法的方法名,value是方法名对应的具体函数方法,如形参和返回值。

https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/rpc/server.go#L283

  1. methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}

register最后把service注册进syc.map。key是服务名,默认是传入的对象rcvr结构的名字,value是service对象,里面其实包含了rcvr的所有具体函数实现。

  1. if _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
  2. return errors.New("rpc: service already defined: " + sname)
  3. }

接着是rpc.HandleHTTP(),本质也是调用http.Handle,参数server就是我们建立的rpc server。当然Server结构体实现了ServeHTTP。

  1. const (
  2. // Defaults used by HandleHTTP
  3. DefaultRPCPath = "/_goRPC_"
  4. DefaultDebugPath = "/debug/rpc"
  5. )
  6. func (server *Server) HandleHTTP(rpcPath, debugPath string) {
  7. http.Handle(rpcPath, server)
  8. http.Handle(debugPath, debugHTTP{server})
  9. }
  10. // HandleHTTP registers an HTTP handler for RPC messages to DefaultServer
  11. // on DefaultRPCPath and a debugging handler on DefaultDebugPath.
  12. // It is still necessary to invoke http.Serve(), typically in a go statement.
  13. func HandleHTTP() {
  14. DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath)
  15. }
  16. // ServeHTTP implements an http.Handler that answers RPC requests.
  17. func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  18. if req.Method != "CONNECT" {
  19. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  20. w.WriteHeader(http.StatusMethodNotAllowed)
  21. io.WriteString(w, "405 must CONNECT\n")
  22. return
  23. }
  24. conn, _, err := w.(http.Hijacker).Hijack()
  25. if err != nil {
  26. log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
  27. return
  28. }
  29. io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
  30. server.ServeConn(conn)
  31. }

DefaultServer是一个全局变量,在进程启动时就已经NewServer()放在全局变量DefaultServer中存储,serviceMap用于存储各个注册好的对象,当一个rpc请求到来时,就从serviceMap中找出对应的处理方法service。我们看下Server结构体的具体定义。

  1. // Server represents an RPC Server.
  2. type Server struct {
  3. serviceMap sync.Map // map[string]*service
  4. reqLock sync.Mutex // protects freeReq
  5. freeReq *Request
  6. respLock sync.Mutex // protects freeResp
  7. freeResp *Response
  8. }
  9. // NewServer returns a new Server.
  10. func NewServer() *Server {
  11. return &Server{}
  12. }
  13. // DefaultServer is the default instance of *Server.
  14. var DefaultServer = NewServer()

service结构体定义如下

  • name:服务名,默认是结构体变量名,如Rect
  • rcvr:reflect.ValueOf()得到
  • typ:reflect.TypeOf()得到
  • method:注册好的方法映射表,key是具体方法名,value是对应的方法实例配置。
  1. type service struct {
  2. name string // name of service
  3. rcvr reflect.Value // receiver of methods for the service
  4. typ reflect.Type // type of the receiver
  5. method map[string]*methodType // registered methods
  6. }

http.ListenAndServe(“:8080”, nil);之后就是开始监听端口,监听网络IO事件。

当一个远程调用request到来时,ServeCodec收到了数据读取的请求,读取解码远程调用request。这里分析一下ServeCodec的主要逻辑,ServeCodec在server被new出来后就执行到,然后在一个for循环内阻塞等待请求。当有请求到达后,readRequest解码出请求的请求service,请求结构体,请求参数等数据,之后开一个协程单独处理这个RPC请求:go service.call(server, sending, wg, mtype, req, argv, replyv, codec)

值得注意的是,ServeCodec使用了sync.WaitGroup来管理RPC请求的响应,当ServeCodec需要关闭时,需要等到sync.WaitGroup内所有响应流程都结束后,再推出这个ServeCodec流程,这就是我们常说的优雅退出。

  1. func (server *Server) ServeCodec(codec ServerCodec) {
  2. sending := new(sync.Mutex)
  3. wg := new(sync.WaitGroup)
  4. for {
  5. service, mtype, req, argv, replyv, keepReading, err := server.readRequest(codec)
  6. if err != nil {
  7. if debugLog && err != io.EOF {
  8. log.Println("rpc:", err)
  9. }
  10. if !keepReading {
  11. break
  12. }
  13. // send a response if we actually managed to read a header.
  14. if req != nil {
  15. server.sendResponse(sending, req, invalidRequest, codec, err.Error())
  16. server.freeRequest(req)
  17. }
  18. continue
  19. }
  20. wg.Add(1)
  21. go service.call(server, sending, wg, mtype, req, argv, replyv, codec)
  22. }
  23. // We've seen that there are no more requests.
  24. // Wait for responses to be sent before closing codec.
  25. wg.Wait()
  26. codec.Close()
  27. }

这里继续深挖service.call的实现:

  • mtype.numCalls用于计数当前service被调用过多少次,自增前需要加锁
  • mtype.method.Func取调用的具体函数方法,取出之后可以通过反射机制调用这个函数,returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
  • 函数返回值是returnValues,是个error类型,而函数执行后的具体回复是放在replyv
  • 最后使用server.sendResponse把函数调用后的结果返回给客户端。
  • server.freeRequest清理请求记录,需要加锁处理,把空的request对象放回请求链表复用。
  1. func (s *service) call(server *Server, sending *sync.Mutex, wg *sync.WaitGroup, mtype *methodType, req *Request, argv, replyv reflect.Value, codec ServerCodec) {
  2. if wg != nil {
  3. defer wg.Done()
  4. }
  5. mtype.Lock()
  6. mtype.numCalls++
  7. mtype.Unlock()
  8. function := mtype.method.Func
  9. // Invoke the method, providing a new value for the reply.
  10. returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
  11. // The return value for the method is an error.
  12. errInter := returnValues[0].Interface()
  13. errmsg := ""
  14. if errInter != nil {
  15. errmsg = errInter.(error).Error()
  16. }
  17. server.sendResponse(sending, req, replyv.Interface(), codec, errmsg)
  18. server.freeRequest(req)
  19. }
  20. func (server *Server) getRequest() *Request {
  21. server.reqLock.Lock()
  22. req := server.freeReq
  23. if req == nil {
  24. req = new(Request)
  25. } else {
  26. server.freeReq = req.next
  27. *req = Request{}
  28. }
  29. server.reqLock.Unlock()
  30. return req
  31. }
  32. func (server *Server) freeRequest(req *Request) {
  33. server.reqLock.Lock()
  34. req.next = server.freeReq
  35. server.freeReq = req
  36. server.reqLock.Unlock()
  37. }

reflect包调用函数call,用的比较少,这里也特意记录学习下。我们调用的是function.Call([]reflect.Value{s.rcvr, argv, replyv}),假设调用的是Rect.Area方法,而处理函数的声明为func (r *Rect) Area(p Params, ret *int) error

https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/reflect/value.go#L335

  1. // Call calls the function v with the input arguments in.
  2. // For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]).
  3. // Call panics if v's Kind is not Func.
  4. // It returns the output results as Values.
  5. // As in Go, each input argument must be assignable to the
  6. // type of the function's corresponding input parameter.
  7. // If v is a variadic function, Call creates the variadic slice parameter
  8. // itself, copying in the corresponding values.
  9. func (v Value) Call(in []Value) []Value {
  10. v.mustBe(Func)
  11. v.mustBeExported()
  12. return v.call("Call", in)
  13. }