Le Go Tour
Pour apprendre le langage, les dévelopepeurs dfe Go on conçu le A Tour of Go. Il s’agit d’un site interactif qui vous fait découvrir les grandes lignes du langage.
Sur cette page, nous reprenons les chapitres de ce tout de Go avec quelques explications.
Les bases
Hello World
Le fameux Hello World en Go peut s’écrire de la manière suivante
package main
import "fmt"
func main() {
fmt.Println("Hello, World")
}
Packages
Tous les programmes en Go sont composés de package. Lorsqu’on
exécute un fichier Go, on exécute la méthode main du package
main.
Les bibliothèques sont importés avec le mot clé import. Dans
l’exemple ci-dessus, on importe la bibliothèque fmt.
Si on importe plusieurs bibliothèques, on peut utiliser plusieurs
import :
import "fmt"
import "math"
ou on peut factoriser le import:
import (
"fmt"
"math"
)
Fonctions publiques/privées
Go n’a pas de mot clé spécifique pour indique qu’une méthode est publique ou privée. C’est simplement la première lettre de l’identifiant qui est utilisé. Si un identifiant commence par une majuscule, il est publique (exporté). Sinon, il est privé et n’est pas visible en dehors du package où il est déclaré.
Fonctions
En Go, les fonctions sont intriduites avec le mot clé funct :
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
Notez que contrairement à C ou Java, le type vient après le nom du paramètre.
Cette syntaxe était déjà utilisé à l’époque du Pascal et est à nouveau le standard
des langages modernes tels que Kotlin, Rust ou Swift. Notez aussi que Go
n’a pas besoin de ; pour indiquer la fin d’une instruction.
Si deux paramètres sont de même type, on peut dimpilifier l’écriture :
func add(x, y int) int {
return x + y
}
Une fonction en Go peut retoruner plusieurs résultats :
func swap(x, y string) (string, string) {
return y, x
}
Cette technique est souvent utilisée pour indiquer si tout c’est bien passé ou si il y a eu une erreur :
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
Variables
Les variables sont déclarés avec le mot clé var :
var x, y int
On peut aussi définir les valeurs initiales :
var x, y int = 1, 2
Go peut inférer le type des variables :
var i, j = 1, 2
A l’intérieur d’une fonction, une variable locale peut aussi
être déclarée et assignée avec l’opérateur := :
func main() {
k := 3
}
Types de base
Le types de base en Go sont les suivants:
boolstringint,int8,int16,int32,int64uint,uint8,uint16,uint32,uint64uintptr(un entier assez grand pour contenir toute sortes de pointeur)byte(alias pouruint8)rune(alias pourint32et représente un caractère Unicode)float32,float64complex64,complex128
Conversions de types
L’expression T(v) convertit la valeur v dans le type T :
i := 42
f := float64(i)
u := uint(f)
Contrairement à C ou Java, Go ne fait pas de conversion implicite de types :
var a int32 = 1
var b int8 = 2
var c int32 = a + b // invalid operation: a + b
// (mismatched types int32 and int8)
Constantes
Les constantes sont déclarés avec le mot clé const :
const Pi = 3.14
Comme pour import, on peut factoriser le mot clé const :
const (
Pi = 3.14
Answer = 42
)
Boucles
En Go, il n’y a qu’une seule instruction pour les boucles : for. Il
n’y a pas de while ou de do/while.
for i := 0; i < 10; i++ {
sum += i
}
for sum < 1000 {
sum += sum
}
Instructions conditionnelles
L’instruction conditionnelle de base en Go est, comme la plupart
des autre langages, le if. Tout comme pour le for, le if
n’a pas desoin de parenthèses :
if x < 1 {
return sqrt(-x) + "i"
}
Un if peut commencer avec une courte instruction avant la condition.
Cette construction est souvent utilisé pour le traitement des erreurs :
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
Notez que dans l’exemple ci-dessus, la variable err n’est pas visible
en dehors du if
Comme la plupart des autres langages, Go implémente le mot clé else
pour définir l’action en cas de condition fausse.
En plus du if/else, Go propose aussi l’instruction switch :
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
Contrairement à C, C++ ou Java (mais comme Kotlin avec le when ou Rust avec le match),
le switch de Go n’exécute que le case correspondant. Vous n’avez donc pas besoin de mettre un
break à la fin des case.
De plus, les case ne doivent pas forcément être des constantes ou des entiers.
Si on ne définit pas d’expression après le switch, Go considère que l’expression est true
et permet ainsi de coder proprement une chaîne de if/else if/... :
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
Le switch n’exécute que le premier case qui correspond.
Defer
L’instruction defer permet de différer l’exécution de code.
Cette construction est souvent utilisée pour fermer des fichiers
ou autres ressources :
// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.
...
}
Pointeurs
Tout comme C ou C++, Go permet l’utilisation de pointeurs (mais contrairement à C++, Go ne propose pas de faire la différence entre un pointeur et une référence).
En Go, le type *T est un pointeur vers T :
var p *int
L’opérateur & permet d’obtenir l’adresse (donc la valeur du pointeur) d’une variable :
i := 42
p = &i
L’opérateur * permet de déréférencer un pointeur :
fmt.Println(*p)
*p = 21
Contrairement à C ou C++, Go ne permet de faire des opérations arithmétiques avec les pointeurs (et c’est une bonne chose).
Structures
Comme en C/C++ ou en Java avec les classes, Go permet de définir un
nouveau type permettant de regrouper des attributs avec le mot clé struct.
type Point struct {
X int
Y int
}
L’utilisation est la même qu’en C/C++ :
p := Point{1, 2}
p.X = 4
Notez que les attributs publiques doivent commencer par des majuscules.
Les struct peuvent aussi s’utiliser avec les pointeurs :
p := Point{1, 2}
r := &p
r.X = 1e9
Notez que contrairement à C/C++, vous n’avez pas besoin de syntaxe
spéciale du genre x->X ou (*r).x pour accéder aux attributs. Go
sait que c’est un pointeur vers un struct et le déréférence
automatiquement pour vous.
On peut initialiser un struct de différentes manières. En spécifiant
tous les attributs :
p1 = Point{1, 2}
en nommant les attributs (dans l’exemple suivant, l’attribut Y vaut
implicitement zéro) :
p2 = Point{X: 1}
en initialisant le tout à zéro :
p3 = Point{}
on peut aussi déclarer un pointeur vers une structure allouée dynamiquement :
r = &Point{1, 2}
Tableaux
On déclare un tableau (array) avec la syntaxe suivante :
var a [10]int
Mais en Go, on utilisera plutôt des slice que des tableaux. Le type
[]T (avec rien entre les crochets) indique un slice de T.
Un slice peut être construit par raport à un tableau (ou un autre slice)
avec l’opération a[low : high] :
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
primes est un tableau de 6 éléments et s est un slice de 3 éléments avec
les valeurs primes[1]. primes[2] et primes[3].
Les slices ne stockent pas de données eux-même mais ne sont que des références vers des tableaux. Pour plus de détails concernant les slices et leur implémentation, consultez la documentation.
On peut créer un slice avec un tableau associé avec le sytaxe suivante :
primes := []int{2, 3, 5, 7, 11, 13}
Contrairement à l’exemple précédent, on n’a pas spécifié de taille entre les crochets et
primes est maintenant un slice et plus un array.
La capacité d’un slice c’est le nombre maximum d’éléments qu’il peut contenir en considérant l’index minimal et la taille du tableau associé. La longueur d’un slice c’est le nombre d’éléments entre l’index minimal et l’index maximal.
La longueur d’un slice est donnée par l’instruction len(s) et la capacité avec
l’instruction cap(s).
On peut créer un slice dynamiquement avec l’instruction make.
L’instruction suivante crée un slice de capacité 5 et de longueur 5. Les éléments du tableau associé sont initialisés à zéro :
a := make([]int, 5)
L’instruction suivante crée un slice de capacité 5 mais de longueur 3.
b := make([]int, 3, 5)
On peut refaire un slice avec la longueur à sa capacité avec :
b = b[:cap(b)]
Un slice peut contenir n’import quel type, y-compris des autres slices
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
On peut ajouter des éléments à la fin d’un slice avec l’instruction append :
var s []int
s = append(s, 0)
s = append(s, 1)
s = append(s, 2, 3, 4)
Notez que append ne modifie pas le slice passé en argument, mais il en retourne
un nouveau. Si la capacité du tableau associé ne permet pas d’ajouter les
éléments, Go créra un tableau plus grand et y copiera les éléments du
tableau original.
Range
L’instruction range permet d’itérer sur tous les éléemnts d’un slice (ou d’une map).
Quand on itère sur un slice, on obtient l’index de chaque élément ainsi qu’une copie
de l’éléemnt (un peu comme l’instruction enumerate de Python) :
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
Si l’indice ou la copie de la valeur n’est pas utilisé, il peut être assigné à _ :
for i, _ := range pow
for _, value := range pow
Si on a besoin que de l’indice, on peut aussi écrire :
for i := range pow
Maps
Une map est un tableau associatif (comme les dict en Python). Il permet d’associer
une valeur à une clé donnée.
type Coordinate struct {
Lat, Long float64
}
var m map[string]Coordinate
func main() {
m = make(map[string]Coordinate)
m["HEIA-FR"] = Coordinate{
46.7926, 7.15993,
}
fmt.Println(m["HEIA-FR"])
}
L’instruction make permet d’initialiser une map.
On peut aussi initialiser une map avec des constantes :
var m = map[string]Coordinate{
"HEIA-FR": Coordinate{
46.7926, 7.15993,
},
"Google": Coordinate{
37.42202, -122.08408,
},
}
ou plus simplement :
var m = map[string]Coordinate{
"HEIA-FR": {46.7926, 7.15993},
"Google": {37.42202, -122.08408},
}
Pour accéder à la valeur correspodant à une clé donnée, la syntaxe est la même que pour les tableaux ou les slices :
elem = m[key]
Il en va de même pour les modifications d’un élément :
m[key] = elem
Pour supprimer un élément, utilisez l’instruction delete :
delete(m, key)
Contrairement aux dict de Python, si on accède à un élément qui n’existe pas dans la map, Go retourne
simplement zéro. Pour faire la différence entre la valeur zéro et un élément qui n’existe pas, on utilise
la syntaxe suivante :
elem, ok = m[key]
ok est un boolean qui indique si la clé key se trouve dans la map.
Si on veut juste savoir si un élément existe sans y accéder, on peut faire :
_, ok = m[key]
Valeurs fonctions
En Go, les fonctions sont aussi des valeurs qui peuvent être assignées à des variables. Ca permet d’imbriquer des fonctions dans des fonctions :
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
}
On peut aussi passer des fonctions comme paramètre à une autre fonction. Par exemple :
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
La fonction compute retourne un float64 et prend une fonction fn en argument, qui elle même prend
deux arguments de type float64 et retourne également un float64. compute évalue la fonction fn
avec les arguments 3 et 4 et retourne le résultat de l’appel de fn.
Une fonction peut aussi retourner une fonction :
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
La fonction adder ci-dessus retourne une fonction qui perend un argument de type int
et qui retrourne un autre int qui est la somme des arguments de tous les appels
précédents. La variable sum reste active même quand adder aura retourné à son appelant
et ne sera visible que par la fonction retournée.
Si on appelle plusieurs fois adder, toutes les fonctions retournées auront leur propre
variable sum.
Cette technique s’appelle Function closure.
Les méthodes et les interfaces
Go n’a pas de classes, mais nous pouvons associer des méthodes aux types. Une méthode c’est juste une fonction avec un paramètre récepteur (receiver argument) :
type Point struct {
X, Y float64
}
func (p Point) DistanceFromOrigin() float64 {
return math.Sqrt(p.X*p.X + p.Y*p.Y)
}
Dans l’exemple ci-dessus, p est le receiver et c’est l’équivalent de this en C++ ou en Java.
La différence c’est que vous pouvez nommer ce receiver comme vous voulez.
En Go, les méthodes ne sont pas limitées à être attaché à des struct. On peut par exemple
attacher une méthode à un simple nombre :
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
Notez que la méthode doit être dans le même package que le type et nous
ne pouvons donc pas attacher de méthode à un type tel que float64. Mais
nous pouvons définir un alias comme ci-dessus.