Why value stored in an interface is not addressabl

2019-01-18 01:02发布

问题:

Citing the golang wiki (https://github.com/golang/go/wiki/MethodSets#interfaces):

"The concrete value stored in an interface is not addressable, in the same way, that a map element is not addressable."

The question of map values not being addressable is explained here: Why are map values not addressable?

However, it is not clear regarding the interface. Why are they not addressable? Is this because of some hard design assumption?

回答1:

Why isn't a non-pointer value stored in an interface addressable? This is an excellent question, and the answer explains why an interface containing a non-pointer value can't be the receiver for a method with a pointer receiver, leading to the dreaded error:

<type> does not implement <interface> (<name> method has pointer receiver)

tl;dr

A non-pointer value stored in an interface isn't addressable to maintain type integrity. For example, a pointer to A, which points to a value of type A in an interface, would be invalidated when a value of a different type B is subsequently stored in the interface.

Because a non-pointer value stored in an interface isn't addressable, the compiler can't pass its address to a method with a pointer receiver.

Long answer

The answers I've seen online don't make much sense. For instance, this article says:

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”).

It's true that the value stored in an interface is not addressable, but as far as I can see it's not because its stored in "a hidden memory location".

Another common answer is:

When an interface value is created, the value that is wrapped in the interface is copied. It is therefore not possible to take its address, and even if you did, using a pointer to the interface value would have unexpected effects (ie. unable to alter the original copied value).

This makes no sense, since a pointer to a value copied into an interface would be no different than a pointer to a value copied into a concrete type; in both cases you can't alter the original copied value through the pointer to the copy.

So why isn't a value stored in an interface addressable? The answer lies in the follow-on implications if it were addressable.

Let's say you have an interface, I, and two types, A and B, which satisfy that interface:

type I interface{}
type A int
type B string

Create an A and store it in an I:

func main() {
    var a A = 5
    var i I = a
    fmt.Printf("i is of type %T\n", i)

Let's pretend we could take the address of a value stored in an interface:

    var aPtr *A
    aPtr = &(i.(A)) // not allowed, but if it were...

Now create a B and store it in i:

    var b B = "hello"
    i = b
    fmt.Printf("i is of type %T, aPtr is of type %T\n", i, aPtr)
}

Here's the output:

i is of type main.A
i is of type main.B, aPtr is of type *main.A

After putting a B into i, what is aPtr pointing to? aPtr was declared as pointing to an A, but t now contains a B, and aPtr is no longer a valid pointer to A.

This, however is permitted:

    var aPtr *A
    var a2 A = i.(A)
    aPtr = &a2

Because the second line makes a copy of the value in i.(A), and aPtr does not point to i.(A).

So, why can't an interface containing a non-pointer value be the receiver for a method with a pointer receiver? Because a non-pointer value stored in an interface isn't addressable, so the compiler can't pass its address to a method with a pointer receiver.



回答2:

I think the answer is "because Go does not have references". If you call Foo(x) you know x won't be modified; if you call Foo(&x), you expect that it might. If what you request would be possible (in particular, if you want the value itself to be addressed, not the copy made in the interface), then that would break down:

func Bar() {
    b := 42
    Foo(b)
    fmt.Println(b)
}

func Foo(v interface{}) {
    // Making up syntax now
    p = takeAddressAs(v).(*int)
    *p = 23
}

Note, that it is possible (from a technical perspective) to address the copy stored in an interface and it definitely would be possible to build a language in which it would be allowed to modify the original value too (like you seem to want): This is basically how Python works. You could just make v = x be syntactic sugar for v = &x when v is of interface type. But that would add reference values to Go, which it intentionally lacks.

I think the root cause of the confusion here is that Go has syntactic sugar to call b.Foo(), even if Foo has a pointer receiver (as long as b is addressable). It's reasonable to be confused that you can call b.Write() but you can't do fmt.Fprintln(b, 42). Arguably, Go should just not have that sugar and instead require you to explicitly do (&b).Write or just make it a pointer to begin with (using, e.g. b := new(bytes.Buffer) instead of var b bytes.Buffer). But the answer to that is a) it's terribly convenient and b) it didn't seem that unexpected, that b.Foo() might modify b.

tl;dr is: a) Go does not have references, b) there is no technical reason it couldn't have them (contrary to the other answers' claims) but c) Go decided not to want them.



回答3:

An interface in Go is the definition of methods that a type must provide if it is to be used as a type of the interface.

The code that uses an interface isn't concerned with how the implementing type does this under the covers, it just cares that the interface methods are satisfied.

As an example, lets suppose I have a simple logger interface that my code uses:

type Logger interface {
    Printf(format string, args ...interface{})
    Errorf(format string, args ...interface{})
}

I can use this interface all through my code. I'll need to supply an implementation of Logger where it's required. There may be several implementations. I may have a FileLogger that writes to a file. Should my code be able to access, for example, the File property in the FileLogger?

I may have a ConsoleLogger and a SockerLogger and a LogglyLogger all of which satisfy the interface. But it makes no sense in any of these to even have a File property. If my code that uses Logger interface was interested in the underlying File, what would it do when using these other Logger implementations?

If you need to access the underlying data in an interface implementation, you are probably using interfaces incorrectly.

A way around this is perhaps using Getter/Setters on the interface? If you really need access to a value from the underlying structure, make that part of the interface. In the example above a GetFile() won't make much sense but perhaps there are other situations where all implementations of an interface share a common attribute e.g. GetLen() and SetLen()?



标签: go