Manipulation des chemins en Rust
2021-06-21
C'est moins simple qu'il n'y paraît.
Rust est un langage très intéressant pour la création d’utilitaires système, où la manipulation de fichiers et chemins est quelque chose d’assez basique et attendu, d’autant plus dans un langage de programmation moderne.
Nous allons dans cet article explorer la manipulation de chemins en rust.
Stade 1: la découverte et le déni🔗
La documentation nous dit que std::path est l’outil adapté.
Tiré de leur documentation, nous avons l’exemple suivant:
use std::path::Path;
use std::ffi::OsStr;
let path = Path::new("/tmp/foo/bar.txt");
let parent = path.parent();
assert_eq!(parent, Some(Path::new("/tmp/foo")));
let file_stem = path.file_stem();
assert_eq!(file_stem, Some(OsStr::new("bar")));
let extension = path.extension();
assert_eq!(extension, Some(OsStr::new("txt")));
En effet, utiliser un Path est bien pratique: pas besoin d’appeller fs::metadata(&str) pour obtenir des informations sur un fichier !
Les choses se compliquent lorsque nous cherchons à afficher ce Path, comme dans le cas de logs applicatifs.
Voici un code, écrit sur le playground rust:
fn main() {
use std::path::Path;
let path: &Path = Path::new("/tmp/foo/bar.txt");
println!("je suis le path {}!", path);
}
La compilation de ce code se solde en échec:
Compiling playground v0.0.1 (/playground)
error[E0277]: `Path` doesn't implement `std::fmt::Display`
--> src/lib.rs:5:37
|
5 | println!("je suis le path {}!", path);
| ^^^^ `Path` cannot be formatted with the default formatter.
|
= help: the trait `std::fmt::Display` is not implemented for `Path`
= note: required by `std::fmt::Display::fmt`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
En effet, si &Path n’implémente pas Display, on ne risque pas de pouvoir l’utiliser tel quel.
Partie 2: la négociation🔗
Peut-être devrions-nous le convertir en &str? La méthode to_str(&self) renvoie un Option<&str>. Pas idéal, mais on doit pouvoir s’en sortir avec un expect ou un unwrap:
fn main() {
use std::path::Path;
let path = Path::new("/tmp/foo/bar.txt");
println!("je suis le path {}!", path.to_str().expect("got None on path.to_str()"));
}
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 1.76s
Ça fonctionne! Super! Mais pourquoi &Path::to_str() renvoie-t-il un Option?
D’après la documentation:
pub fn to_str(&self) -> Option<&str>Yields a
&strslice if thePathis valid unicode.This conversion may entail doing a check for UTF-8 validity. Note that validation is performed because non-UTF-8 strings are perfectly valid for some OS, like Windows.
Ah. Donc si jamais mon code tourne sur un windows, j’aurai une panic! à chaque fois que je tenterais d’afficher un &Path. Pas très glorieux.
Partie 3: la colère🔗
En lisant la documentation de &Path et PathBuf, on se rend compte que c’est à peine plus qu’un wrapper autour de OsStr, une abstraction sur le format des chaines de caractère selon la plateforme.
Quel intérêt il y a-t-il à utiliser &Path, si on doit utiliser une gestion complexe d’erreurs sur de simples chemins?! Ne serions-nous pas mieux à passer tous les chemins en tant que &str et écrire des fonctions de manipulation de chemin selon la plateforme? N’est-ce pas le rôle qu’est supposé remplir std::Path?!
De plus, considérons le code suivant:
let p: &Path = Path::new("/tmp");
let s: &str = p.join("myfile.txt").to_str().expect("nique windows");
Ce code ne compilera pas car Path::join créé une valeur temporaire, libérée à la fin de l’expression, rendant notre &str caduque. Nous sommes obligés de passer par les variantes objet de Path et str, rajoutant un overhead non désirable.
Partie 4: la dépression🔗
Pourquoi de si simples opérations courantes sont-elles si compliquées dans un langage moderne?
Pourquoi la librairie standard ne propose t-elle pas de moyens pratiques d’effectuer ces conversions?
Une informatique précise et exact telle que le propose rust n’est pas compatible avec la facilité d’usage.
Il n’y a pas d’espoir, nous devons accepter notre sort de devoir réinventer la roue à chaque nouveau cycle de création et souffrir en silence, tel Sisyphe et son rocher.
Partie 5: l’acceptation🔗
Struct
std::path::Display🔗
pub struct Display<'a> { /* fields omitted */ }Helper struct for safely printing paths with
format!and{}.A
Pathmight contain non-Unicode data. This struct implements theDisplaytrait in a way that mitigates that. It is created by the display method onPath. This may perform lossy conversion, depending on the platform. If you would like an implementation which escapes the path please useDebuginstead.Examples🔗
use std::path::Path; let path = Path::new("/tmp/foo.rs"); println!("{}", path.display());
fn main() {
use std::path::Path;
let path = Path::new("/tmp/foo/bar.txt");
println!("je suis le path {}!", path.display());
let s: String = path.display().to_string();
println!("je suis la string {}!", s);
}
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 2.05s
Running `target/debug/playground`
je suis le path /tmp/foo/bar.txt!
je suis la string /tmp/foo/bar.txt!
Oh.
tip
sujets: [ dev | rust | informatique ]