Почему Go допускает присвоение интерфейсных переменных значениям, которые не реализуют получатели значений? - PullRequest
0 голосов
/ 22 февраля 2019

Извините за запутанный заголовок вопроса, но я кое-что не понимаю.В следующем коде ошибка достигается, когда мы пытаемся присвоить x для p, потому что x требует p для реализации M(), а это не так, поскольку M() имеет приемник указателя.

type Person struct {
    Name string
    Age int
}

func (p *Person) M() {
}

type I interface {
    M()
}

func main() {
    var x I
    var p := Person{}
    x = p              // Error, M() has pointer receiver
}

Это имеет для меня относительный смысл.Вещь, которую я не понимаю, это то, что совершенно приятно назначать x на &p в следующем примере.В этом примере M() имеет получатель value , а не указатель, но он все еще работает просто отлично.

type Person struct {
    Name string
    Age int
}

func (p Person) M() {
}

type I interface {
    M()
}

func main() {
    var x I
    var p := Person{}
    x = &p             // No error
}

Это не имеет смысла для меня.Метод реализует либо получатель значения, либо получатель указателя, поэтому, если он не позволяет вам присвоить переменную интерфейса значению типа, который реализует только получатель указателя, почему он позволил бы вам назначить его указателю,когда метод определен только как получатель значения?

Наконец, это еще более смущает тот факт, что в Go можно без проблем выполнить следующее:

func (p *Person) rename(string newName) {
    p.Name = newName
}

func main() {
    p := Person{Name: "Old name"}
    fmt.Println(p.Name)
    p.rename("New name")
    fmt.Println(p.Name) // Name has changed, despite p not being pointer
}

В туре Go, он говорит, что вызовы, подобные этому, явно преобразуются (например, p.rename("New name") неявно становится (&p).rename("New name"), а указатели становятся значениями, если необходимо).

Это кажется действительно противоречивым.Я здесь не прав?

1 Ответ

0 голосов
/ 24 февраля 2019

Я понял это.Короче, я испортил две концепции.У меня было много путаницы, поэтому давайте разберем ее, начав с моего последнего замечания об автоматической разыменовке указателя.

Основная проблема, с которой я столкнулся, заключалась в том, что компилятор с удовольствием применил & оператор для вас.Дело в том, что бывают моменты, когда это имеет смысл, а иногда нет (см. Ниже).

p := Person{"Old"}
var iface I = p
p.rename("New") // Can safely be rewritten to (&p).rename("New")
iface.rename("New") // Cannot rewrite (&iface != &p)

Вот почему имеет смысл запретить присваивать значение интерфейс, если только указатель на это значение удовлетворяет интерфейсу.Вы не можете просто вернуть адрес исходной переменной, когда она назначена интерфейсу, но вы можете это сделать, когда она является переменной действительного типа.Теперь, это все еще оставило меня в замешательстве по этому поводу:

p := Person{"Name"}
var iface I = &p    // No error

То, что я получил от этого, в основном это то, что ... Go просто работает таким образом. Этот пост описывает наборы методов.Я был сбит с толку словом "адресуемый".Чтобы объяснить мою путаницу, у меня сложилось впечатление, что компилятор делал следующую вставку, когда указатель хранился, но было необходимо значение (что не имело бы смысла):

p := &Person{"Name"}
var iface I = p
p.rename()  /* becomes */ (*p).rename()
iface.rename() /* becomes */ (*iface).rename()

Выполнение методавызов - это многошаговый процесс с точки зрения компилятора;простое добавление * перед именем переменной может показаться далеко идущим, но есть еще больше переменных, которые можно скопировать из памяти и поместить в стек и т. д. В действительности, разыменование переменной самостоятельно не «делает» ничего длякомпилятор.В обоих случаях необходимо перейти в это место и скопировать то, что там есть.Физическое написание * - это, с точки зрения Go, всего лишь указание компилятору делать то, что он уже собирается делать, потому что метод получателя значения ожидает значение, поэтому компилятору необходимо его получить.

...