Nie można przekonwertować ciągu [] na interfejs [] {}

97

Piszę jakiś kod i potrzebuję go do przechwytywania argumentów i przekazywania ich fmt.Println
(chcę jego domyślnego zachowania, zapisywania argumentów oddzielonych spacjami i zakończonych znakiem nowej linii). Jednak trwa, []interface {}ale flag.Args()zwraca []string.
Oto przykład kodu:

package main

import (
    "fmt"
    "flag"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args()...)
}

To zwraca następujący błąd:

./example.go:10: cannot use args (type []string) as type []interface {} in function argument

Czy to błąd? Nie powinienem fmt.Printlnbrać żadnej tablicy? Swoją drogą, też próbowałem to zrobić:

var args = []interface{}(flag.Args())

ale pojawia się następujący błąd:

cannot convert flag.Args() (type []string) to type []interface {}

Czy istnieje sposób obejścia tego problemu?

cruizh
źródło
1
Bawiłem się prostym przykładem ( go run test.go some test flags) i wydawało się, że działa przy zmianie flags.Args()...na just flag.Args()(na wyjściu znajduje się znak [some test flags]nowej linii; również wydawało się, że działa z rejestrowaniem rzeczywistych flag). Nie będę udawać, że rozumiem, dlaczego, a odpowiedź Stephena i tak jest o wiele bardziej pouczająca :)
RocketDonkey

Odpowiedzi:

117

To nie jest błąd. fmt.Println()wymaga []interface{}typu. Oznacza to, że musi to być wycinek interface{}wartości, a nie „dowolny wycinek”. Aby przekonwertować wycinek, musisz zapętlić i skopiować każdy element.

old := flag.Args()
new := make([]interface{}, len(old))
for i, v := range old {
    new[i] = v
}
fmt.Println(new...)

Powodem, dla którego nie możesz użyć żadnego wycinka, jest to, że konwersja między a []stringi a []interface{}wymaga zmiany układu pamięci i odbywa się w czasie O (n). Konwersja typu na typ interface{}wymaga czasu O (1). Gdyby ta pętla for była niepotrzebna, kompilator nadal musiałby ją wstawić.

Stephen Weinberg
źródło
2
Nawiasem mówiąc, znalazłem ten link w golang-nuts: groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
cruizh
2
Tak, każda iteracja wymaga O (1) czasu, a pętla wymaga O (n) czasu. To właśnie powiedziałem. Jeśli chodzi o funkcję otrzymującą a []string, oczekuje a interface{}. An interface{}ma inny układ pamięci niż a, stringwięc problemem jest fakt, że każdy element musi zostać przekonwertowany.
Stephen Weinberg
1
@karlrh: Nie, przypuśćmy, że Printlnfunkcja modyfikuje wycinek i ustawia niektóre elementy (nie robi, ale przypuśćmy, że tak). Następnie może włożyć interface{}do plastra dowolny , który powinien mieć tylko strings. To, czego naprawdę chcesz, to coś w rodzaju symbolu wieloznacznego Java Generics Slice<? extends []interface{}>, ale nie istnieje w Go.
newacct
4
Dołączanie jest magiczne. Jest wbudowany i traktowany specjalnie przez język. Inne przykłady obejmują new(), len()i copy(). golang.org/ref/spec#Appending_and_copying_slices
Stephen Weinberg
1
Dziś dowiedziałem się, że „nowy” nie jest słowem kluczowym dotyczącym rezerwy! Nie jest też marka!
Sridhar
11

Jeśli chcesz wydrukować tylko kawałek ciągów, możesz uniknąć konwersji i uzyskać dokładnie ten sam wynik, łącząc:

package main

import (
    "fmt"
    "flag"
    "strings"
)

func main() {
    flag.Parse()
    s := strings.Join(flag.Args(), " ")
    fmt.Println(s)
}
Moshe Revah
źródło
11

W takim przypadku konwersja typu jest niepotrzebna. Po prostu przekaż flag.Args()wartość do fmt.Println.


Pytanie:

Nie można przekonwertować ciągu [] na interfejs [] {}

Piszę jakiś kod i potrzebuję go do przechwytywania argumentów i przekazywania ich przez fmt.Println (chcę jego domyślnego zachowania, zapisywania argumentów oddzielonych spacjami i zakończonych znakiem nowej linii).

Oto przykład kodu:

package main

import (
    "fmt"
    "flag"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args()...)
}

Flaga pakietu

import "flag"

func Args

func Args() []string

Args zwraca argumenty wiersza poleceń niebędące flagami.


Pakiet fmt

import "fmt"

func Println

func Println(a ...interface{}) (n int, err error)

Printlnformatuje używając domyślnych formatów dla swoich operandów i zapisuje na standardowe wyjście. Spacje są zawsze dodawane między operandami, a znak nowej linii jest dołączany. Zwraca liczbę zapisanych bajtów i wszelkie napotkane błędy zapisu.


W takim przypadku konwersja typu jest niepotrzebna. Po prostu przekaż flag.Args()wartość do fmt.Println, która używa odbicia, aby zinterpretować wartość jako typ []string. Pakiet reflectimplementuje odbicie w czasie wykonywania, umożliwiając programowi manipulowanie obiektami o dowolnych typach. Na przykład,

args.go:

package main

import (
    "flag"
    "fmt"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args())
}

Wynik:

$ go build args.go
$ ./args arg0 arg1
[arg0 arg1]
$ 
peterSO
źródło
Gdybym chciał pominąć nawiasy, czy konwersja nadal byłaby niepotrzebna?
cruizh
1

W Go funkcja może akceptować tylko argumenty typu określonego na liście parametrów w definicji funkcji. Wariadyczna funkcja języka parametrów nieco to komplikuje, ale działa zgodnie z dobrze zdefiniowanymi regułami.

Podpis funkcji dla fmt.Println:

func Println(a ...interface{}) (n int, err error)

Zgodnie ze specyfikacją językową ,

Ostatni parametr przychodzący w sygnaturze funkcji może mieć typ poprzedzony przedrostkiem .... Funkcja z takim parametrem nazywana jest wariadyczną i może być wywoływana z zerem lub większą liczbą argumentów dla tego parametru.

Oznacza to, że możesz przekazać Printlnlistę argumentów interface{}typu. Ponieważ wszystkie typy implementują pusty interfejs, możesz przekazać listę argumentów dowolnego typu, dzięki czemu możesz Println(1, "one", true)na przykład wywołać bez błędu. Zobacz sekcję „Przekazywanie argumentów do ... parametrów” specyfikacji języka:

przekazana wartość to nowy wycinek typu [] T z nową tablicą bazową, której kolejne elementy są faktycznymi argumentami, z których wszystkie muszą być przypisane do T.

Część, która sprawia kłopoty, znajduje się zaraz po tej w specyfikacji:

Jeśli ostatni argument można przypisać do typu wycinka [] T, może zostać przekazany niezmieniony jako wartość parametru ... T, jeśli po argumencie występuje .... W tym przypadku nie jest tworzony nowy wycinek.

flag.Args()jest typem []string. Ponieważ Tw Printlnto interface{}, []Tto []interface{}. Zatem pytanie sprowadza się do tego, czy wartość wycinka łańcucha można przypisać do zmiennej typu wycinka interfejsu. Możesz to łatwo sprawdzić w swoim kodzie go, próbując przypisać, na przykład:

s := []string{}
var i []interface{}
i = s

Jeśli spróbujesz takiego przypisania, kompilator wyświetli następujący komunikat o błędzie:

cannot use s (type []string) as type []interface {} in assignment

Dlatego nie możesz użyć wielokropka po kawałku łańcucha jako argumentu funkcji fmt.Println. To nie jest błąd, działa zgodnie z przeznaczeniem.

Nadal istnieje wiele sposobów można drukować flag.Args()z Printlntakich jak

fmt.Println(flag.Args())

(który zostanie wyświetlony jako [elem0 elem1 ...], zgodnie z dokumentacją pakietu fmt )

lub

fmt.Println(strings.Join(flag.Args(), ` `)

(co spowoduje wyświetlenie elementów wycinka ciągu, każdy oddzielony pojedynczą spacją) , na przykład przy użyciu funkcji Join w pakiecie ciągów z separatorem ciągu.

jrefior
źródło
0

Myślę, że przy użyciu refleksji jest to możliwe, ale nie wiem, czy to dobre rozwiązanie

package main

import (
    "fmt"
    "reflect"
    "strings"
)

type User struct {
    Name string
    Age  byte
}

func main() {
    flag.Parse()
    fmt.Println(String(flag.Args()))
    fmt.Println(String([]string{"hello", "world"}))
    fmt.Println(String([]int{1, 2, 3, 4, 5, 6}))
    u1, u2 := User{Name: "John", Age: 30},
        User{Name: "Not John", Age: 20}
    fmt.Println(String([]User{u1, u2}))
}

func String(v interface{}) string {
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Array || val.Kind() == reflect.Slice {
        l := val.Len()
        if l == 0 {
            return ""
        }
        if l == 1 {
            return fmt.Sprint(val.Index(0))
        }
        sb := strings.Builder{}
        sb.Grow(l * 4)
        sb.WriteString(fmt.Sprint(val.Index(0)))
        for i := 1; i < l; i++ {
            sb.WriteString(",")
            sb.WriteString(fmt.Sprint(val.Index(i)))
        }
        return sb.String()
    }

    return fmt.Sprintln(v)
}

Wynik:

$ go run .\main.go arg1 arg2
arg1,arg2
hello,world
1,2,3,4,5,6
{John 30},{Not John 20}
Juan Torres
źródło
-1

fmt.Printlnprzyjmuje parametr wariadyczny

func Println (a ... interfejs {}) (n int, błąd błędu)

Możliwe jest drukowanie flag.Args()bez konwersji do formatu[]interface{}

func main() {
    flag.Parse()
    fmt.Println(flag.Args())
}
Shahriar
źródło
2
fmt.PrintlnPodpis nie zmienił się od ponad 6 lat (a to była tylko zmiana pakietu error). Nawet gdyby tak było, specyfikacja wyraźnie mówi: „wariadyczny z końcowym parametrem p typu ... T, to wewnątrz f typ p jest równoważny typowi [] T.”, więc nie miałoby to znaczenia. fmt.Println(flags.Args()...)nadal nie działa (brakuje ci rozszerzenia plasterka), fmt.Println(flags.Args())zawsze działał.
Marc
Skąd masz informację, że podpis fmt.Println nie zmienił się od ponad 6 lat? Czy sprawdziłeś kod źródłowy?
Shahriar
Komentarz na temat operacji sugeruje, że używanie flag.Args()wyłącznie jako argumentu do fmt.Printlncofnięcia się w tamtym czasie. (Byłoby zaskakujące, gdyby tak nie było w tym czasie)
leaf bebop
Więc? Jeśli istnieje komentarz, to znaczy, że moja odpowiedź powinna zostać odrzucona?
Shahriar