Deep Introduction to Go Interfaces.
Standard Interface Intro
Go’s interfaces are one of it’s best features, but they’re also one of the most confusing for newbies. This post will try to give you the understanding you need to use Go’s interfaces and not get frustrated when things don’t work the way you expect. It’s a little long, but a bunch of that is just code examples.
Go’s interfaces are different than interfaces in other languages, they are implicitly fulfilled. This means that you never need to mark your type as explicitly implementing the interface (like class CFoo implements IFoo). Instead, your type just needs to have the methods defined in the interface, and the compiler does the rest.
For example:
type Walker interface {
Walk(miles int)
}
type Camel struct {
Name string
}
func (c Camel) Walk(miles int) {
fmt.Printf(“%s is walking %v miles\n”, c.Name, miles)
}
func LongWalk(w Walker) {
w.Walk(500)
w.Walk(500)
}
func main() {
c := Camel{“Bill”}
LongWalk(c)
}
// prints
// Bill is walking 500 miles.
// Bill is walking 500 miles.
http://play.golang.org/p/erodX-JplO
Camel implements the Walker interface, because it has a method named Walk that takes an int and doesn’t return anything. This means you can pass it into the LongWalk function, even though you never specified that your Camel is a Walker. In fact, Camel and Walker can live in totally different packages and never know about one another, and this will still work if a third package decides to make a Camel and pass it into LongWalk.
Non-Standard Continuation
This is where most tutorials stop, and where most questions and problems begin. The problem is that you still don’t know how the interfaces actually work, and since it’s not actually that complicated, let’s talk about that.
What actually happens when you pass Camel into LongWalk?
So, first off, you’re not passing Camel into LongWalk. You’re actually assigning c, a value of type Camel to a value w of type Walker, and w is what you operate on in LongWalk.
Under the covers, the Walker interface (like all interfaces), would look more or less like this if it were in Go (the actual code is in C, so this is just a really rough approximation that is easier to read).
type Walker struct {
type InterfaceType
data *void
}
type InterfaceType struct {
valtype *gotype
func0 *func
func1 *func
...
}
All interfaces values are just two pointers - one pointer to information about the interface type, and one pointer to the data from the value you passed into the interface (a void in C-like languages… this should probably be Go’s unsafe.Pointer, but I liked the explicitness of two actual *’s in the struct to show it’s just two pointers).
The InterfaceType contains a pointer to information about the type of the value that you passed into the interface (valtype). It also contains pointers to the methods that are available on the interface.
When you assign c to w, the compiler generates instructions that looks more or less like this (it’s not actually generating Go, this is just an easier-to-read approximation):
data := c
w := Walker{
type: &InterfaceType{
valtype: &typeof(c),
func0: &Camel.Walk
}
data: &data
}
When you assign your Camel value c to the Walker value w, the Camel type is copied into the interface value’s Type.valtype field. The actual data in the value of c is copied into a new place in memory, and w’s Data field points at that memory location.
Implications of the Implementation
Now, let’s look at the implications of this code. First, interface values are very small - just two pointers. When you assign a value to an interface, that value gets copied once, into the interface, but after that, it’s held in a pointer, so it doesn’t get copied again if you pass the interface around.
So now you know why you don’t need to pass around pointers to interfaces - they’re small anyway, so you don’t have to worry about copying the memory, plus they hold your data in a pointer, so changes to the data will travel with the interface.
Interfaces Are Types
Let’s look at Walker again, this is important:
type Walker interface
Note that first word there: type. Interfaces are types, just like string is a type or Camel is a type. They aren’t aliases, they’re not magic hand-waving, they’re real types and real values which are distinct from the type and value that gets assigned to them.
Now, let’s assume you have this function:
func LongWalkAll(walkers []Walker) { for _, w := range walkers { LongWalk(w) } }
And let’s say you have a caravan of Camels that you want to send on a long walk:
caravan := []Camel{ Camel{“Bill”}, Camel{“Bob”}, Camel{“Steve”}}
You want to pass caravan into LongWalkAll, will the compiler let you? Nope. Why is that? Well, []Walker is a specific type, it’s a slice of values of type Walker. It’s not shorthand for “a slice of anything that matches the Walker interface”. It’s an actual distinct type, the way []string is different from []int. The Go compiler will output code to assign a single value of Camel to a single value of Walker. That’s the only place it’ll help you out. So, with slices, you have to do it yourself:
walkers := make([]Walker, len(caravan))
for n, c := range caravan {
walkers[n] = c
}
LongWalkAll(walkers)
However, there’s a better way if you know you’ll just need the caravan for passing into LongWalkAll:
caravan := []Walker{ Camel{“Bill”}, Camel{“Bob”}, Camel{“Steve”}}
LongWalkAll(caravan)
Note that this goes for any type which includes an interface as part of its definition: there’s no automatic conversion of your func(Camel) into func(Walker) or map[string]Camel into map[string]Walker. Again, they’re totally different types, they’re not shorthand, and they’re not aliases, and they’re not just a pattern for the compiler to match.
Interfaces and the Pointers That Satisfy Them
What if Camel’s Walk method had this signature instead?
func (c *Camel) Walk(miles int)
This line says that the type *Camel has a function called Walk. This is important: *Camel is a type. It’s the “pointer to a Camel” type. It’s a distinct type from (non-pointer) Camel. The part about it being a pointer is part of its type. The Walk method is on the type *Camel. The Walk method (in this new incarnation) is not on the type Camel. This becomes important when you try to assign it to an interface.
c := Camel{“Bill”}
LongWalk(c)
// compiler output:
cannot use c (type Camel) as type Walker in function argument:
Camel does not implement Walker (Walk method has pointer receiver)
To pass a Camel into LongWalk now, you need to pass in a pointer to a Camel:
c := &Camel{“Bill”}
LongWalk(c)
or
c := Camel{“Bill”}
LongWalk(&c)
Note that this true even though you can still call Walk directly on Camel:
c := Camel{“Bill”}
c.Walk(500) // this works
The reason you can do that is that the Go compiler automatically converts this line to (&c).Walk(500) for you. However, that doesn’t work for passing the value into an interface. The reason is that the value in an interface is in a hidden memory location, and so the compiler can’t automatically get a pointer to that memory for you (in Go parlance, this is known as being “not addressable”).
Nil Pointers and Nil Interfaces
The interaction between nil interfaces and nil pointers is where nearly everyone gets tripped up when they first start with Go.
Let’s say we have our Camel type with the Walk method defined on *Camel as above, and we want to make a function that returns a Walker that is actually a Camel (note that you don’t need a function to do this, you can just assign a *Camel to a Walker, but the function is a good illustrative example):
func MakeWalker() Walker {
return &Camel{“Bill”}
}
w := MakeWalker()
if w != nil {
w.Walk(500) // we will hit this
}
This works fine. But now, what if we do something a little different:
func MakeWalker(c *Camel) Walker {
return c
}
var c *Camel
w := MakeWalker(c)
if w != nil {
// we’ll get in here, but why?
w.Walk(500)
}
This code will also get inside the if statement (and then panic, which we’ll talk about in a bit) because the returned Walker value is not nil. How is that possible, if we returned a nil pointer? Well, let’s go look back to the instructions that get generated when we assign a value to an interface.
data := c
w := Walker{
type: &InterfaceType{
valtype: &typeof(c),
func0: &Camel.Walk
}
data: &data
}
In this case, c is a nil pointer. However, that’s a perfectly valid value to assign to the Walker’s Data value, so it works just fine. What you return is a non-nil Walker value, that has a pointer to a nil *Camel as its data. So, of course, if you check w == nil, the answer is false, w is not nil… but then inside the if statement, we try to call Camel’s walk:
func (c *Camel) Walk(miles int) {
fmt.Printf(“%s is walking %v miles\n”, c.Name, miles)
}
And when we try to do c.Name, Go automatically turns that into (*c).Name, and the code panics with a nil pointer dereference error.
Hopefully this makes sense, given our new understanding of how interfaces wrap values, but then how do you account for nil pointers? Assume you want MakeWalker to return a nil interface if it gets passed a nil Camel. You have to explicitly assign nil to the interface:
func MakeWalker(c *Camel) Walker {
if c == nil {
return nil
}
return c
}
var c *Camel
w := MakeWalker(c)
if w != nil {
// Yay, we don’t get here!
w.Walk(500)
}
And now, finally, the code is doing what we expect. When you pass in a nil *Camel, we return a nil interface. Here’s an alternate way to write the function:
func MakeWalker(c *Camel) Walker {
var w Walker
if c != nil {
w = c
}
return w
}
This is slightly less optimal, but it shows the other way to get a nil interface, which is to use the zero value for the interface, which is nil.
Note that you can have a nil pointer value that satisfies an interface. You just need to be careful not to dereference the pointer in your methods. For example, if *Camel’s Walk method looked like this:
func (c *Camel) Walk(miles int) {
fmt.Printf(“I’m walking %d miles!”, miles)
}
Note that this method does not dereference c, and therefore you can call it even if c is nil:
var c *Camel
c.Walk(500)
// prints “I’m walking 500 miles!”
source POST: http://npf.io/2014/05/intro-to-go-interfaces/
Deep Introduction to Go Interfaces.的更多相关文章
- Function接口 – Java8中java.util.function包下的函数式接口
Introduction to Functional Interfaces – A concept recreated in Java 8 Any java developer around the ...
- Interface => IDataErrorInfo
Introduction to common Interfaces IDataErrorInfo Provides the functionality to offer custom error in ...
- 39. Volume Rendering Techniques
Milan Ikits University of Utah Joe Kniss University of Utah Aaron Lefohn University of California, D ...
- Implement GAN from scratch
GANs from Scratch 1: A deep introduction. With code in PyTorch and TensorFlow 修改文章代码中的错误后的代码如下: impo ...
- linux 虚拟网络设备的使用
1. linux 常见虚拟网络设备分类 常见虚拟网络设备有:bridge, tun/tap, veth-pairs, macvlan, macvtap等.有一篇博文写的挺好的,图文并茂:虚拟网络设备, ...
- A beginner’s introduction to Deep Learning
A beginner’s introduction to Deep Learning I am Samvita from the Business Team of HyperVerge. I join ...
- Introduction to Deep Neural Networks
Introduction to Deep Neural Networks Neural networks are a set of algorithms, modeled loosely after ...
- 李宏毅机器学习笔记4:Brief Introduction of Deep Learning、Backpropagation(后向传播算法)
李宏毅老师的机器学习课程和吴恩达老师的机器学习课程都是都是ML和DL非常好的入门资料,在YouTube.网易云课堂.B站都能观看到相应的课程视频,接下来这一系列的博客我都将记录老师上课的笔记以及自己对 ...
- 【DeepLearning学习笔记】Coursera课程《Neural Networks and Deep Learning》——Week1 Introduction to deep learning课堂笔记
Coursera课程<Neural Networks and Deep Learning> deeplearning.ai Week1 Introduction to deep learn ...
随机推荐
- Eclipse系列:如何设置Eclipse关联JDK源码和文档
一.设置Eclipse关联JDK源码 1.打开Eclipse-->Windows-->Preferences 2. 在弹出的Preferences对话框中,Java--> ...
- VB.NET,C#.NET调用Web Service,利用visual studio 的实现方法
下面是一篇文章比较详细,其实具体操作很简单,把Web Service服务地址,利用工具(VS2010),通过添加引用的形式,添加到项目中来就可以应用了. 大家如果这个地方不会操场的话,可以问问我QQ: ...
- Java Math.sqrt()方法
描述 java.lang.Math.sqrt(double a) 返回正确舍入的一个double值的正平方根.特殊情况: 如果参数是NaN或小于为零,那么结果是NaN. 如果参数是正无穷大,那么结果为 ...
- [javase学习笔记]-6.4 成员变量与局部变量
前面我们学习了类的定义,我们不难理解,定义类事实上就是在定义类中的成员. 成员包含成员变量和成员函数. 说到成员变量,我们非常自然会想到前面提到过的局部变量,那么它们之间有什么差别呢? 首先我们定义一 ...
- Java 解决 servlet 接收参数中文乱码问题
方法一: 接收到的参数进行如下操作[不建议]: String tmp = new String(type.getBytes("iso-8859-1"), "utf-8&q ...
- Using Timers in MFC Applications
Timer Events in MFC Applications Event timers are always handy to have around and useful in nearly e ...
- IIS发布网站遇到 编译器错误消息: CS0016: 未能写入输出文件“c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary 编
编译错误: 说明:在编译向该请求提供服务所需资源的过程中出现错误.请检查下列特定错误详细信息并适当地修改源代码. 编译器错误消息:CS0016: 未能写入输出文件“c:\Windows\Microso ...
- 关于Puppet不得不说的故事
Puppet对于做DevOps的同学来说,是个熟悉的名字,但仍有许多人并不了解它.那么我先来简单介绍一下:Puppet是由Puppetlabs公司开发的系统管理框架和工具集,被用于IT服务的自动化管理 ...
- Redis源代码剖析--对象object
前面一系列的博客分析了Redis的基本数据结构,有动态字符串sds.双端链表sdlist.字典dict.跳跃表skiplist.整数集合intset和压缩列表ziplist等,这些数据结构对于用户来说 ...
- mysql查询结果单位换算后小数位数的保留方式
1.调用mysql自带的格式化小数函数format(x,d) 例如: select format(23456.789,2); select formate(salary,2); 输出: 23,45 ...