Contexte du problème
Dans la bibliothèque standard, la méthode Vec::first renvoie Option<&T>. Cette signature reflète le fait qu’un vecteur peut être vide, même lorsqu’une fonction précédente a garanti la présence d’au moins un élément. L’exemple de get_configuration_directories montre ce schéma : la fonction lit la variable d’environnement CONFIG_DIRS, construit un Vec, puis utilise anyhow::ensure! pour lever une erreur si le vecteur est vide. Malgré cette validation, le code appelant doit encore gérer le cas None retourné par first(), ce qui introduit une duplication de logique et un risque de désynchronisation si l’invariant change.
Solution avec le type NonEmpty
Le crate nonempty propose la structure NonEmpty :
pub struct NonEmpty { pub head: T, pub tail: Vec, }Le constructeur new exige un élément initial, empêchant la création d’une instance vide. La méthode first(&self) -> &T renvoie directement la référence du head sans enveloppe Option. La conversion depuis un Vec s’effectue via NonEmpty::from_vec, qui renvoie Option> ; le None indique que le vecteur d’origine était vide et doit être traité avant de construire le NonEmpty. En réécrivant get_configuration_directories pour retourner Result>, le code client devient : let config_dirs = get_configuration_directories()?; initialize_cache(config_dirs.first())?;Il n’est plus nécessaire de vérifier l’existence d’un premier élément, la garantie étant assurée par le système de types.Exemples d’utilisation dans l’écosystème Rust
Le même principe apparaît dans la réécriture Rust des utilitaires POSIX : la structure Pipeline contient commands: NonEmpty, ce qui oblige le parseur à produire une pipeline uniquement lorsqu’au moins une commande a été reconnue. Le code du parseur crée d’abord un NonEmpty::new(command) puis ajoute les commandes suivantes, garantissant ainsi que Pipeline ne peut jamais être vide. Un autre cas réel provient de rust-analyzer, où le type AbsPathBuf encapsule Utf8PathBuf et implémente TryFrom : la conversion échoue si le chemin n’est pas absolu, transformant ainsi la validation en une étape de construction typée.
Analyse des impacts
En déplaçant la validation dans le type, le code gagne en lisibilité : chaque appel à first() est explicite et ne nécessite plus de branchement conditionnel. Sur le plan de la performance, l’élimination du Option évite un niveau d’indirection et un test de présence à l’exécution. Le principal coût réside dans la conversion initiale (from_vec) qui doit inspecter le vecteur pour extraire le premier élément, mais cette opération est déjà réalisée dans la plupart des scénarios où l’on construit le NonEmpty. Enfin, le modèle renforce la robustesse : toute modification de l’invariant (par exemple autoriser une configuration vide) impose une mise à jour du type de retour, rendant les régressions détectables à la compilation.