如何在导入的包中定义结构体时使用Go接收器?

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

How to use go receiver when struct is defined in imported package

问题

我目前正在使用vishvananda/netns包,尝试从特定的网络命名空间中提取路由。

当我请求特定网络命名空间的“handle”时,会返回一个定义好的Handle结构体,如下所示:

func NewHandleAt(ns netns.NsHandle, nlFamilies ...int) (*Handle, error)

然后,这个Handle结构体作为接收器参数传递给需要该handle的函数:

func (h *Handle) LinkList() ([]Link, error)

我对Go语言还不熟悉,不确定如何将它们联系在一起。我卡在了这里:

func (h *Handle) showInts() {
  ints, err := h.netlink.LinkList()
  if err != nil {
      log.Fatal(err)
  }

  for i, r := range ints {
      log.Printf("%d: %s", i, r.Attrs().Name)
  }
}


func main() {
    ints, err := netlink.LinkList()
    if err != nil {
        log.Fatal(err)
    }
    for i, r := range ints {
        log.Printf("%d: %s", i, r.Attrs().Name)
    }

    pid, err := netns.GetFromPid(9097)
    if err != nil {
        log.Fatal(err)
    }

    netlink.NewHandleAt(pid)
    showInts()
}
英文:

currently working with the vishvananda/netns package trying to extract routes from a specific network namespace.

There is a defined Handle struct which is returned when I request a 'handle' for a specific network namespace. As such:

func NewHandleAt(ns netns.NsHandle, nlFamilies ...int) (*Handle, error)

This is then a receiver argument (?) to a function that requires that handle,

func (h *Handle) LinkList() ([]Link, error)

I'm new to go and not sure how to tie these together. I'm stuck with:

func (h *Handle) showInts() {
  int, err := h.netlink.LinkList()
  if err != nil {
      log.Fatal(err)
  }

  for i, r := range int {
      log.Printf("%d: %s", i, r.Attrs().Name)
  }
}


func main() {
    ints, err := netlink.LinkList()
    if err != nil {
        log.Fatal(err)
    }
    for i, r := range ints {
        log.Printf("%d: %s", i, r.Attrs().Name)
    }

    pid, err := netns.GetFromPid(9097)
    if err != nil {
        log.Fatal(err)
    }

    netlink.NewHandleAt(pid)
    showInts()
}

答案1

得分: 7

更新

在撰写原始答案时,涉及了许多内容,没有明确的结构,所以这里有一个更有结构的版本:

根据你实际提问的是什么(例如“如何向导出的类型添加一个接收器函数/方法”或“到底什么是接收器函数”),答案如下:

如何向导出的类型添加一个接收器函数?

很简单,就像你对任何其他类型做的一样。实际上,你已经接近了。这样是不行的:

func (h *Handler) showInts() {}

因为你正在向你的包中的Handler类型添加一个方法。假设你有一个main函数,那就是main包。你正在尝试将其添加到netlink.Handler类型中。在这种情况下,这样做是可以的:

func (h *netlink.Handler) showInts(){}

毕竟,在你的主包中,类型是netlink.Handler...然而,这样是不行的。编译器会拒绝编译,并告诉你:“无法在非本地类型上定义新方法”。不过,这很容易通过创建一个新类型并在其中添加方法来解决:

type MyHandler netlink.Handler
func (h *MyHandler) showInts(){}

不管怎样,你代码中的最后两行让我感到不对劲。由于NewHandleAt返回(*Handle, error),而netlink.Handle是一个接收器参数,正确的方式应该是:

var mh *MyHandle
if h, err := netlink.NewHandleAt(pid); err != nil {
    log.Fatal(err) // 出错了
} else {
    mh = (*MyHandle)(h)
}
mh.showInts() // 在类型为*MyHandle的mh上调用showInts方法

将外部类型包装在自定义类型中意味着你会经常进行类型转换假设`netlink.Handle`有一个`Test`方法你想在`showInts`内部调用它

```go
func (h *MyHandle) showInts() {
    nh := (*netlink.Handle)(h) // 需要进行类型转换
    nh.Test()
}

我还会将变量名从pid改为nsh或其他什么,因为它是一个NsHandle,而不是一个pid...


什么是接收器参数?

因为你写了这个:

这是一个接收器参数(?),用于一个需要该句柄的函数,

我觉得你对接收器参数不是完全清楚。简单来说,它就像一个函数参数,但不是只传递给函数的参数,而是保存了调用该函数的对象/值的参数。基本上,它是函数/方法被调用的“实例”。可以将其视为许多面向对象编程语言中的this关键字:

func (h *MyHandle) showInts() {
    return
}

在类似C++的语言中,它可能是这样的:

class MyHandle : Handle
{
    public:
        void showInts(void) { return; } // 将h替换为this
}

然而,有一些重要的区别:

  • 接收器参数可以是指针,也可以是值 - 对于值接收器,方法不能修改接收器的值
  • 没有私有、公有或受保护的概念...至少不是传统的面向对象方式
  • ...

有很多不同之处,也许考虑一下学习一下Go语言的基础知识。关于Go方法的内容可以在这里找到。


其他问题/奇怪的事情

再次查看你的代码后,我真的不确定这个是正确的:

h.netlink.LinkList()

在你的main函数中,你调用了netlink.LinkList()h是一个*netlink.Handler。如果你需要调用netlink.LinkList函数,很可能h.netlink.LinkList不是你想要做的。相反,你应该简单地调用netlink.LinkList()
这是假设你需要首先调用该函数。

既然你已经在main函数中调用了它,为什么不将其作为参数传递呢?

// 在main函数中:
ints, err := netlink.LinkList()
// ...
h.showInts(ints)

func (h *MyHandle)showInts(ll []netlink.Link) {
}
英文:

Update

While writing the original answer, touched on a number of things, without any clear structure, so here's a more structured version:

Depending on what you're actually asking (ie "How do I add a receiver function/method to an exported type", or "What the hell is a receiver function"), the answers are as follows:

How do I add a receiver function to an exported type?

Easy, same as you do with any other type. You were close, in fact. This doesn't work:

func (h *Handler) showInts() {}

Because you're adding a method to the Handler type in your package. Given you have a main function, that would be the main package. You're trying to add it to the netlink.Handler type instead. In which case, this will work:

func (h *netlink.Handler) showInts(){}

The type is netlink.Handler in your main package after all... This, however will not work. The compiler will refuse to compile, telling you: "Cannot define new methods on non-local type". This is easily mitigated, though, by creating a new type, and add the method there:

type MyHandler netlink.Handler
func (h *MyHandler) showInts(){}

Be that as it may, the last 2 lines in your code strike me as wrong.
Given that NewHandleAt returns (*Handle, error), and netlink.Handle is a receiver argument, the correct way would be:

var mh *MyHandle
if h, err := netlink.NewHandleAt(pid); err != nil {
    log.Fatal(err) // something went wrong
} else {
    mh = (*MyHandle)(h)
}
mh.showInts() // call showInts on mh, which is of type *MyHandle

The fact that you've "wrapped" the external type in a custom type does mean you'll find yourself casting the same thing quite a lot. Say netlink.Handle has a Test method, and you want to call it inside showInts:

func (h *MyHandle) showInts() {
    nh := (*netlink.Handle)(h) //cast required
    nh.Test()
}

I'd also change the varname from pid to nsh or something, because it's a NsHandle, and not a pid after all...


What is a receiver argument?

Because you wrote this:

> This is then a receiver argument (?) to a function that requires that handle,

I get the impression you're not entirely clear on what a receiver argument is. Put simply, it's like a function argument, but instead of an argument that is just passed to a function, it's an argument that holds the object/value on which the function is called. Basically, it's the "instance" on which the function/method is called. Think of it as the this keyword in many OOP languages:

func (h *MyHandle) showInts() {
    return
}

In something like C++ would be

class MyHandle : Handle
{
    public:
        void showInts(void) { return; } // replace h with this
}

There are significant differences, however:

  • The receiver argument can be a pointer, or a value - in case of a value receiver, the method cannot modify the receiver value
  • There's no such thing as private, public, or protected... at least not in the traditional OO way
  • ...

There's quite a few differences, perhaps consider going through the golang tour. The stuff about go methods can be found here


Other issues/weird things

After looking at your code again, I'm really not sure whether this is correct:

h.netlink.LinkList()

In your main function, you call netlink.LinkList(). h is a *netlink.Handler. If you need to call the netlink.LinkList function, it's highly likely h.netlink.LinkList is not what you want to do. Instead, you should simply call netlink.LinkList().<br>
That's assuming you need to call the function in the first place.

Given that you've already called it in the main function, why not pass it as an argument?

//in main:
ints, err := netlink.LinkList()
//...
h.showInts(ints)

func (h *MyHandle)showInts(ll []netlink.Link) {
}

答案2

得分: 1

感谢Elias,答案很棒!

根据这个答案,我编写了以下代码,它将列出属于特定命名空间的接口。谢谢!

package main

import (
    "github.com/vishvananda/netns"
    "github.com/vishvananda/netlink"
    "log"
)

type NSHandle netlink.Handle

func (h *NSHandle) showInts() {
  nh := (*netlink.Handle)(h) //cast required
  int, err := nh.LinkList()
  if err != nil {
      log.Fatal(err)
  }

  log.Printf("Namespace Ints:")
  for i, r := range int {
      log.Printf("%d: %s", i, r.Attrs().Name)
  }
}

func getNSFromPID(pid int) (*NSHandle) {
  hpid, err := netns.GetFromPid(9115)
  if err != nil {
      log.Fatal(err)
  }
  var nsh *NSHandle
  if h, err := netlink.NewHandleAt(hpid); err != nil {
      log.Fatal(err) // something went wrong
  } else {
      nsh = (*NSHandle)(h)
  }
  return nsh
}

func main() {
  getNSFromPID(9115).showInts()
}
英文:

Thanks Elias, awesome answer!

From that, I've written the following code which will list interfaces belonging to a specific namespace. Thanks!

package main

import (
    &quot;github.com/vishvananda/netns&quot;
    &quot;github.com/vishvananda/netlink&quot;
    &quot;log&quot;
)

type NSHandle netlink.Handle

func (h *NSHandle) showInts() {
  nh := (*netlink.Handle)(h) //cast required
  int, err := nh.LinkList()
  if err != nil {
      log.Fatal(err)
  }

  log.Printf(&quot;Namespace Ints:&quot;)
  for i, r := range int {
      log.Printf(&quot;%d: %s&quot;, i, r.Attrs().Name)
  }
}

func getNSFromPID(pid int) (*NSHandle) {
  hpid, err := netns.GetFromPid(9115)
  if err != nil {
      log.Fatal(err)
  }
  var nsh *NSHandle
  if h, err := netlink.NewHandleAt(hpid); err != nil {
      log.Fatal(err) // something went wrong
  } else {
      nsh = (*NSHandle)(h)
  }
  return nsh
}

func main() {
  getNSFromPID(9115).showInts()
}

huangapple
  • 本文由 发表于 2016年12月29日 20:06:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/41379417.html
匿名

发表评论

匿名网友

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

确定