2013-07-11 11 views
9

Mi piace il fatto che Go non mi dia un milione di modi per fare cose semplici - prendere a prestito da The Zen of Python, "Ci dovrebbe essere uno - e preferibilmente solo uno - modo ovvio per farlo. "Modo preferito per istanziare i tipi in Go

Tuttavia, non sono chiaro sul modo preferito/idiomatico di istanziare i tipi. I tipi di base sono semplici:

n := 0 
t := 1.5 
str := "Hello" 

Ma per quanto riguarda le strutture? Sono i seguenti equivalenti, e se sì, quale è preferito e perché?

var f Foo  
f := Foo{} 

E le fette? Posso fare var xs []int, xs := []int{}, o xs := make([]int), ma penso che la prima opzione (al contrario delle strutture) sia diversa dalle altre? Presumo che questo si applichi anche alle mappe.

Con i puntatori, ho sentito che new dovrebbe essere evitato. Questo è un buon consiglio e, in caso affermativo, cosa può valere come utilizzo valido di new?

Mi rendo conto che questo può in parte essere una questione di stile, ma un razionale per preferire uno stile particolare sarebbe utile in ogni caso.

risposta

7

Quando si dichiara una variabile, in cui T è un certo tipo:

var name T 

Go ti dà un pezzo di non inizializzata "azzerato" memoria.

Con i primitivi, ciò significa che var name int sarebbe 0 e var name string sarebbe "". In C it might be zeroed, or might be something unexpected. Go garantisce che una variabile non inizializzata è equivalente a zero del tipo.

Internamente sezioni, mappe e canali vengono considerati come puntatori. Il valore zero dei puntatori è nullo, il che significa che punta a zero memoria. Senza inizializzarlo, puoi incontrare il panico se provi ad operare su di esso.

La funzione make è specificamente progettata per una sezione, una mappa o un canale. Gli argomenti della funzione make sono:

make(T type, length int[, capacity int]) // For slices. 
make(T[, capacity int]) // For a map. 
make(T[, bufferSize int]) // For a channel. How many items can you take without blocking? 

A fette length è il numero di elementi che inizia con. La capacità è la memoria allocata prima che sia necessario un ridimensionamento (internamente, nuova dimensione * 2, quindi copia). Per ulteriori informazioni, vedere Effective Go: Allocation with make.

Strutturazioni: new(T) equivale a &T{}, non T{}. *new(T) equivale a *&T{}.

Fette: make([]T,0) è equivalente a []T{}.

Mappe: make(map[T]T) è equivalente a map[T]T{}.

Per quanto riguarda quale metodo è preferito, mi pongo la seguente domanda:

conosco il valore (s) in questo momento all'interno della funzione?

Se la risposta è "sì", vado con uno dei precedenti T{...}. Se la risposta è "no", allora uso make o new.

Per esempio, vorrei evitare qualcosa di simile:

type Name struct { 
    FirstName string 
    LastName string 
} 

func main() { 
    name := &Name{FirstName:"John"} 
    // other code... 
    name.LastName = "Doe" 
} 

Invece vorrei fare qualcosa di simile:

func main() { 
    name := new(Name) 
    name.FirstName = "John" 
    // other code... 
    name.LastName = "Doe" 
} 

Perché? Perché utilizzando new(Name) ho chiarito che I intende per riempire i valori in seguito. Se avessi usato non sarebbe chiaro che intendevo aggiungere/modificare un valore più tardi nella stessa funzione senza leggere il resto del codice.

L'eccezione è con le strutture quando non si desidera un puntatore. Userò T{}, ma non inserirò nulla se ho intenzione di aggiungere/modificare i valori. Ovviamente anche *new(T) funziona, ma è come usare *&T{}. T{} è più pulito in questo caso, anche se io tendo ad usare i puntatori con le strutture per evitare di fare una copia quando lo faccio passare.

Un'altra cosa da tenere a mente, un []*struct è più piccolo e più economico per ridimensionare rispetto []struct, assumendo la struct è molto più grande di un puntatore, che in genere è di 4 - (? 8 byte su 64bit) 8 byte.

4

Si potrebbe dare un'occhiata alle fonti della libreria standard Go in cui è possibile trovare molti codici Go idiomatici.

Hai ragione: var xs []int differisce dalle altre due varianti in quanto non "inizializza" xs, xs è nullo. Mentre gli altri due costruiscono davvero una fetta. xs := []int{} è comune se hai bisogno di una sezione vuota con zero cap mentre make ti offre più opzioni: lunghezza e capacità. D'altra parte è normale iniziare con una fetta nullo e riempire aggiungendo come in var s []int; for ... { s = append(s, num) }.

new non può essere evitato totale in quanto è l'unico modo per creare un puntatore, ad es. a uint32 o agli altri tipi di builtin. Ma hai ragione, scrivere a := new(A) è piuttosto raro e scritto principalmente come a := &A{} in quanto può essere convertito in a := &A{n: 17, whatever: "foo"}. L'utilizzo di new non è davvero scoraggiato, ma vista l'abilità di struct letterals sembra proprio un residuo di Java per me.

4

Durante la chat di Fireside con il Go Team di Google I/O, qualcuno del pubblico ha chiesto al team di Go cosa volevano estrarre dalla lingua.

Rob ha detto che desiderava c'era meno modo per dichiarare variabili e menzionato:

Colon è uguale per la sovrascrittura, parametri dei risultati denominati (https://plus.google.com/+AndrewGerrand/posts/LmnDfgehorU), variabili riutilizzati in un ciclo for essere fonte di confusione, in particolare per le chiusure. Tuttavia, probabilmente la lingua non cambierà molto.

+0

+1 Non risponde alla domanda, ma sono sicuro che sono d'accordo - felice di sapere che lo ha detto Pike stesso. Questa è una debolezza che sto riscontrando con GoLang: troppi modi per dichiarare e non chiarire i vantaggi e gli inconvenienti e l'appropriatezza di ciascuno di essi - mi dà talvolta una sensazione di "non proprio finito". – Vector

Problemi correlati