Косвенный тип интерфейса в Go - PullRequest
0 голосов
/ 26 июня 2018

Я пытаюсь создать функцию, которая создаст новый экземпляр интерфейса, и назначить этот экземпляр переменной, которая имеет тип интерфейса. Вот простой пример программы (которая не компилируется):

package main

import (
    "fmt"
)

type Foo interface {
    Foo(int) int
}

type Foo_impl struct {}

func (f *Foo_impl) Foo(x int) int {
    return x * 2
}

func main() {
    var x *Foo_impl
    constructFoo(x)

    fmt.Println("Hello, playground")
}

func constructFoo(x Foo) {
    *x = Foo_impl{} // Blows up here - invalid indirect of x (type Foo)
}

Возможно ли с помощью отражения косвенно изменить интерфейсную переменную и присвоить базовому значению? Если бы я не использовал интерфейсы, я бы сделал что-то вроде этого,

func main() {
    var x int
    foo(&x)
    fmt.Printf("%d\n", x)
}

func foo(x *int) {
    *x = 4
}

И, как и ожидалось, это распечатает 4. Проблема в том, что переменные интерфейса не могут быть перенаправлены обычным способом. Есть ли способ обойти это?

Ответы [ 6 ]

0 голосов
/ 26 июня 2018

Мне удалось написать функцию, которая сделала то, что я хочу

package main

import (
    "fmt"
    "reflect"
)

type Y interface {
    SetX(int)
}

type X struct {
    test int
}

func (x *X) SetX(param int) {
    x.test = param
}

func main() {
    var x *X
    y := foo(&x)
    y.SetX(12)
    fmt.Printf("%+v", x)
}

func foo(x interface{}) Y {
    t := reflect.TypeOf(x)
    pointerType := t.Elem()
    realType := pointerType.Elem()

    pointer := reflect.New(realType)
    reflect.Indirect(reflect.ValueOf(x)).Set(pointer)

    return pointer.Interface().(Y)
}

Функция foo может инициализировать любой двойной указатель на тип, который реализует Y, и возвращает новый экземпляр как Y.

0 голосов
/ 26 июня 2018

Еще один подход с varadic функцией, которая принимает несколько нулевых указателей на Foo,

package main

import (
"fmt"
)

type Foo interface {
    Foo(int) int
}

type Foo_impl struct {
    id int
}

func (f *Foo_impl) Foo(x int) int {
    return x * 2
}



func main() {
    var x *Foo_impl
    var x1 = []Foo{x}
    constructFoo(x1...)


    fmt.Println("Hello, playground %v", x1[0])
}

func constructFoo(x ...Foo) {
    for i, foo := range x {
        switch  (foo).(type) {
            case *Foo_impl:
                x[i] = &Foo_impl{2}
        }
    }

}
0 голосов
/ 26 июня 2018

Вот решение для ваших требований, однако указатель типа, который передается в ваш метод конструктора, не может быть nil, один из способов решения этой проблемы - использовать экземпляр по умолчанию.

package main

import (
"fmt"
)


var defaultFooImpl = &Foo_impl{}

type Foo interface {
    Foo(int) int
}

type Foo_impl struct {
    id int
}

func (f *Foo_impl) Foo(x int) int {
    return x * 2
}



func main() {
    var x *Foo_impl = defaultFooImpl
    constructFoo(x)

    fmt.Println("Hello, playground %v", x)
}

func constructFoo(x Foo) {
    switch value :=x.(type) {
        case *Foo_impl:
            *value = Foo_impl{2}
    }

}
0 голосов
/ 26 июня 2018

Реализация интерфейса поможет вам передать фиктивные структуры в вашу функцию, а затем, используя утверждение типа, вы можете получить значение struct. По сути, интерфейс - это единственный способ, которым вы можете обернуть ваш любой тип и передать его функции, а затем, используя утверждения типа, вы можете получить базовое значение.

package main

import (
        "fmt"
)

type Foo interface {
    Foo(int) int
}

type Foo_impl struct {}

func (f *Foo_impl) Foo(x int) int {
    return x * 2
}

func main() {
    var x *Foo_impl
    constructFoo(x)
}

func constructFoo(x interface{}) {
    fmt.Println(x.(interface{}).(*Foo_impl).Foo(10)) // dereference the type to call the function  on pointer receiver
}

Также необходимо разыменовать значение типа struct, переданное в конструктор, для вызова метода с использованием указателя приемника.

Проверьте рабочий код на Go Playground

На Голанге Утверждения типа определяется как:

Для выражения x типа интерфейса и типа T первичный выражение

x.(T)

утверждает, что x не равен nil и что значение, хранящееся в x, имеет тип T. Обозначение x. (T) называется утверждением типа.

Точнее, если T не является типом интерфейса, x. (T) утверждает, что динамический тип x идентичен типу T. В этом случае T должен реализовать тип интерфейса (x); в противном случае утверждение типа неверно, так как для x невозможно сохранить значение типа T. Если T является типом интерфейса, x. (T) утверждает, что динамический тип x реализует интерфейс T.

0 голосов
/ 26 июня 2018

В Go, если у нас есть тип

type Foo_impl struct {}

Мы обычно используем

func NewFoo_impl() *Foo_impl

для создания этого экземпляра этой структуры (при необходимости)

Нет экземпляра интерфейса, мы просто говорим, что тип реализует интерфейс или нет.

Таким образом, ваш код может быть

var x Foo
x = NewFoo_impl()
// or x = &Foo_impl{}

Что касается косвенного типа интерфейса, его нетрудно понять, зная его точно так же, как void* в C.

Разыменование не возвращает тип, который вам нужен, фактически компилятор также не знает, как с ним обращаться. Это стало неполным типом, таким образом, решение Го запрещает это.

0 голосов
/ 26 июня 2018

Но почему ты не можешь быть более идиоматичным и делать

func constructFoo() Foo {
    return &Foo_impl{} 
}

тогда, в основном:

func main() {
    fmt.Println(constructFoo().Foo(10))
}

Также, есть прием интерфейсов, возвратные структуры подход, который может быть вам интересен.

Надеюсь, это немного поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...