№ 04Le piègeThe trap4 min
Prendre String « par habitude » là où &str suffisait.Taking String 'out of habit' where &str would do.
Le réflexe vient d'autres langages : un paramètre de type « chaîne », on écrit le type chaîne — String. Mais en Rust, String dit « je veux la possession ». Si la fonction ne fait que lire, cette exigence ne sert à rien : elle force l'appelant à céder sa donnée ou à cloner, refuse les littéraux, et couple toute l'API à une allocation tas dont elle n'avait pas besoin. Le coût n'est pas dans la fonction — il est chez tous ceux qui l'appellent.The reflex comes from other languages: a 'string' parameter, so you write the string type — String. But in Rust, String says 'I want ownership'. If the function only reads, that demand buys nothing: it forces the caller to give up their data or clone, refuses literals, and couples the whole API to a heap allocation it never needed. The cost isn't in the function — it's on everyone who calls it.
Le contre-exemple — String exigé pour rienThe counter-example — String demanded for nothing
// Une fonction qui ne fait que LIRE, mais exige de posséder
fn est_palindrome(mot: String) -> bool {
let inverse: String = mot.chars().rev().collect();
mot == inverse
}
let titre = String::from("kayak");
est_palindrome(titre.clone()); // clone « pour que ça passe »
est_palindrome(titre); // ou move : on perd titre
// est_palindrome("ressasser") ne compile même pas : &str ≠ StringLa fonction lit mot, compare, et c'est tout — elle n'a aucune raison de le posséder. Pourtant sa signature l'exige, et le coût se propage : l'appelant clone (une allocation entière pour un test booléen), ou cède sa variable et la perd, ou n'a tout simplement pas de String sous la main et reste bloqué. Une mauvaise signature taxe tout le code autour d'elle.The function reads mot, compares, and that's all — it has no reason to own it. Yet its signature demands it, and the cost spreads: the caller clones (a whole allocation for a boolean test), or surrenders their variable and loses it, or simply has no String on hand and is stuck. A bad signature taxes all the code around it.
Le bon réflexe — accepter une vueThe right reflex — accept a view
// La même logique, mais on emprunte une vue
fn est_palindrome(mot: &str) -> bool {
let inverse: String = mot.chars().rev().collect();
mot == inverse
}
let titre = String::from("kayak");
est_palindrome(&titre); // &String → &str, titre intact
est_palindrome("ressasser"); // littéral, directement
est_palindrome(&titre[1..4]); // une sous-tranche — zéro allocUn seul caractère change — String devient &str — et tout se débloque : le &String est accepté par déréférence, le littéral passe directement, la sous-tranche aussi, et titre survit à l'appel. Zéro clone, zéro allocation, plus d'appelants servis. La fonction n'a rien perdu : elle ne faisait que lire, elle ne demande plus que de lire.One character changes — String becomes &str — and everything unlocks: the &String is accepted by deref, the literal passes directly, the sub-slice too, and titre survives the call. Zero clone, zero allocation, more callers served. The function lost nothing: it only ever read, now it only asks to read.
La règle, généraliséeThe rule, generalized
Pour les API : accepte &str et &[T] en entrée, retourne String et Vec<T> en sortie. On exige le minimum de l'appelant, on lui rend une donnée qu'il possède. C'est la convention idiomatique — emprunte ce que tu lis, possède ce que tu produis.For APIs: accept &str and &[T] as input, return String and Vec<T> as output. You demand the minimum from the caller, you hand back data they own. That's the idiomatic convention — borrow what you read, own what you produce.
L'exception qui confirme.The exception that confirms.Prends String en paramètre quand la fonction a vraiment besoin de garder la donnée — la stocker dans une struct, la renvoyer transformée par move, la pousser dans un Vec qui lui survit. Là, exiger la possession est honnête : tu la demandes parce que tu vas vraiment la garder. Le piège n'est pas String — c'est String quand tu ne fais que lire.Take String as a parameter when the function genuinely needs to keep the data — store it in a struct, return it moved-and-transformed, push it into a Vec that outlives the call. There, demanding ownership is honest: you ask for it because you'll really keep it. The trap isn't String — it's String when you only read.
Demander String pour lire, c'est exiger les clés de la maison pour regarder par la fenêtre.Demanding String to read is asking for the keys to the house just to look through the window.