Welcome here! | Articles | Main projects | About me
(FR) Go : Un point sur le dossier “internal”
Sommaire
- Introduction : un problème de visibilité
- D’accord, mais comment on fait en Go ?
- “internal”, kézako ?
- En résumé
Introduction : un problème de visibilité
Lorsque l’on développe un module Go, il est évident que l’on souhaite exposer certains types et certaines fonctions ; autrement, notre module n’a pas grand intérêt, vous en conviendrez.
Toutefois, certains éléments doivent rester internes, tout en étant accessibles au code de notre module.
Typiquement, en C#, il existe le mot-clé internal
, qui rend un type / méthode / property / field (rayer les mentions inutiles) uniquement visible depuis l’assembly courante.
Ainsi, les détails d’implémentation restent bien au chaud, inaccessibles en dehors de leur assembly.
D’accord, mais comment on fait en Go ?
En C#, nous avons des tas mots-clés concernant la visibilité : public
, private
, protected
, internal
, et depuis peu file
.
Néanmoins, en Go, il n’existe que deux types de visibilité : public (toute fonction / type / variable dont le nom commence par une majuscule est visible en dehors du package courant), et privé (invisible hors du package courant si le nom commence par une minuscule) :
Exemple :
package example
// Le type Worker est public
type Worker interface {
// La fonction DoWork() est publique
DoWork()
}
// Le type workerImplementation est privé
// Il satisfait toutefois l'interface Worker
type workerImplementation struct {
}
func (wi workerImplementation) DoWork() {
fmt.Println("doing some stuff...")
}
// La fonction NewWorker est publique, et ce
// bien qu'elle renvoie une instance de workerImplementation.
func NewWorker() Worker {
wi := workerImplementation{}
return wi
}
Dans le code ci-dessus, nous avons un exemple de code où l’on veut rendre une interface publique mais son implémentation privée.
Mais alors, comment faire cela à l’échelle d’un module, où l’implémentation est dans un dossier distinct ?
“internal”, kézako ?
Il est possible de reproduire en Go un comportement similaire à celui proposé par le mot-clé internal
en C#.
Pour ce faire, il suffit de créer un dossier, nommé… internal
.
Tout code présent dans ce dossier ou l’un de ses sous-dossiers est visible uniquement dans l’arborescence partant du dossier parent.
J’ai créé un repo GitHub vous permettant de tester ce comportement, accessible à cette URL.
Si vous voulez essayer, créez un projet Go et exécutez la commande ci-après :
go get -u github.com/vpenando/visibility
Créez un fichier main.go
et collez-y le code suivant :
package main
import (
"github.com/vpenando/visibility"
)
func main() {
w := visibility.NewWorker()
w.DoWork()
}
Vous vous en doutez, ce code compilera sans soucis, et affichera même le texte doing some stuff...
; rien d’exceptionnel jusque là.
Si nous jetons un oeil au fichier worker.go
, nous constatons qu’il expose une interface Worker
et une fonction NewWorker
.
Mais surtout, il fait référence à github.com/vpenando/visibility/internal/worker
!
Essayons donc de remplacer le contenu de main.go
par le code suivant :
package main
import (
"github.com/vpenando/visibility/internal/worker"
)
func main() {
w := worker.WorkerImplementation{}
w.DoWork()
}
Dès la compilation, nous aurons alors l’erreur suivante :
main.go:4:2: use of internal package github.com/vpenando/visibility/internal/worker not allowed
C’est exactement le comportement attendu : le package github.com/vpenando/visibility/internal/worker
n’est pas directement accessible depuis notre projet !
Plus exactement, pour citer le papier de 2014 sur le sujet :
An import of a path containing the element “internal” is disallowed if the importing code is outside the tree rooted at the parent of the “internal” directory.
Ainsi, le contenu de notre dossier internal
créé plus haut est accessible depuis le reste du module, étant créé à la racine.
En résumé
En dépit de son faible système de visibilité, Go propose une alternative offrant davantage de finesse dans la gestion de l’interface publique d’un module.
En effet, tout élément créé dans un dossier internal
(ou l’un de ses sous-dossiers) sera au mieux visible dans le dossier parent, et tenter de l’importer depuis -par exemple- un projet tiers résultera en une erreur de compilation.