← L'édition
DevNouveau3 min de lecture

Odin face à Go : une alternative impérative sans ramasse-miettes

Utilisateur de Go (Golang) depuis dix ans, un développeur présente Odin, un langage de programmation système qui reprend la simplicité de syntaxe de Go tout en basculant sur une gestion manuelle de la mémoire. Sans ramasse-miettes (garbage collector), Odin introduit une gestion d'erreurs par unions de types et un système d'allocateurs personnalisables.

Fil « Comparaison et recommandation du langage Odin pour les développeurs Go »

Une syntaxe inspirée de Go enrichie de contrôle bas niveau

Le langage Odin se positionne comme un croisement entre la simplicité de Go et le contrôle d'un langage système. Selon l'auteur, sa structure comprend 60 % de concepts issus de Go, 20 % de fonctionnalités absentes de Go, 10 % de programmation vectorielle (array programming) et 10 % de gestion mémoire bas niveau et d'interfaces FFI (Foreign Function Interface, le mécanisme permettant d'appeler du code C). Bien qu'il intègre des bibliothèques 3D, sa bibliothèque standard fournit les briques nécessaires au développement de services backend.

Odin s'articule autour de 31 mots-clés, associés à des directives (précédées du symbole #) et à des attributs (@). La compilation s'exécute directement en ligne de commande sans passer par un fichier de configuration de type go.mod ou un gestionnaire de paquets dédié : l'importation de bibliothèques externes s'effectue en copiant le répertoire du paquet et en indiquant son chemin relatif.

odin build .
odin run main.odin -file

L'abandon du ramasse-miettes et la typologie des erreurs

L'absence de ramasse-miettes (garbage collector, le composant logiciel qui libère automatiquement la mémoire inutilisée) retire des fonctionnalités de haut niveau comme les goroutines (les threads légers de Go) ou les fermetures de fonctions (closures). Dans Odin, le type d'erreur classique de Go est remplacé par des énumérations (enums), des structures et des unions de types (union #shared_nil).

Cette architecture permet d'imbriquer les types d'erreurs et de les inspecter via des instructions de sélection (switch), tout en générant une trace d'exécution (stack trace, la liste des appels de fonctions) explicite sans exposer les détails d'implémentation internes aux utilisateurs :

Payment_Error :: enum { None, Bank_Account_Is_Empty }
Pay_Half_Debt_Error :: union #shared_nil { Payment_Error, json.Marshal_Error }

#partial switch save_house in err {
case Save_House_Error :
    pay_half := save_house.(Pay_Half_Debt_Error)
    #partial switch pay in pay_half {
    case Payment_Error :
        #partial switch pay {
        case .Bank_Account_Is_Empty :
            fmt.println("Message destiné à l'utilisateur.")
        }
    }
}

Allocation manuelle et gestion des ressources

En l'absence d'automatisation, les allocations mémoire s'effectuent à l'aide des fonctions new() et free() pour les pointeurs simples, ou make() et delete() pour les tableaux dynamiques et les cartes associatives (maps). Pour limiter les fuites de mémoire (memory leaks, lorsque de la mémoire allouée n'est plus restituée au système), les fonctions d'allocation s'appuient sur un allocateur passé dans le contexte (context.allocator).

La bibliothèque standard core:mem met à disposition plusieurs stratégies d'allocation, telles que le Tracking_Allocator pour identifier les fuites ou le virtual.Arena (un allocateur par arène qui regroupe les allocations dans un bloc unique). Cette dernière approche permet de libérer l'ensemble des pointeurs d'une structure de données complexe en un seul appel :

arr_arena : virtual.Arena
arena_allocator := virtual.arena_allocator(&arr_arena)

// Alloue le tableau dynamique et ses éléments dans l'arène
arr := make([dynamic]^int, allocator = arena_allocator)
n := new(int, allocator = arena_allocator)
append(&arr, n)

// Libère toute la mémoire allouée dans l'arène en une seule opération
virtual.arena_destroy(&arr_arena)

Enfin, Odin ne propose pas d'interfaces orientées objet classiques au sens de Go ou Java. Celles-ci sont construites manuellement en associant des pointeurs bruts (rawptr) à des pointeurs de fonctions au sein de structures, ou en imitant des classes abstraites.