Gestion des erreurs

La méthode Account::from_string() ne gère pas les erreurs et cela se reflète par le fait qu'elle renvoie systématiquement un Account. Le code suivant provoquera probablement un panic!() :

fn main() {
  println!("{:?}", Account::from_string("johndoe"));
}

Afin de pouvoir gérer les erreurs, nous devons choisir quelle erreur remonter à l'appelant lorsqu'aucun : n'est trouvé. Nous allons définir un type vide NoColon dans le module account qui nous servira pour signaler une erreur :

pub struct NoColon;

Cela nous permettra de renvoyer éventuellement un type Result<Account, NoColon> lorsque cela sera approprié.

  1. Créez ce type NoColon dans le module account.

FromStr

Plutôt que modifier la méthode Account::from_string(), nous allons utiliser le trait prédéfini FromStr pour convertir un &str en Account en implémentant FromStr pour Account.

  1. Implémentez FromStr pour Account et renvoyez une erreur NoColon si aucun : n'est trouvé.

Depuis main(), il devrait être possible de faire :

fn main() {
  match Account::from_str("johndoe") {
    Ok(account) => println!("{account:?}"),
    Err(e) => println!("Erreur {e:?}"),
  }
}

Vous noterez que l'affichage de l'erreur n'est pas possible ; en effet, le type NoColon n'implémente pas Debug.

  1. Faîtes dériver Debug automatiquement pour le type NoColon et vérifiez que le programme fonctionne comme attendu.

  2. Vous pouvez supprimer la méthode Account::from_string() que nous n'utiliserons plus.

  3. Changez le type de retour de main() pour qu'il soit maintenant Result<(), NoColon>. Utilisez ? pour retourner prématurément en cas d'erreur. Cela se passera bien car NoColon implémente Debug, ce qui est obligatoire si on veut l'utiliser dans le type de retour de main() pour pouvoir afficher l'erreur qui a provoqué la fin de main().

fn main() -> Result<(), NoColon> {
  println!("{:?}", Account::from_str("johndoe")?);
  Ok(())
}

Gestion de la ligne de commande

  1. En utilisant l'itérateur std::env::args() et la méthode collect(), construisez dans main() un vecteur (Vec) de Account à partir des arguments donnés sur la ligne de commande et affichez ce vecteur (si les éléments implémentent Debug, le vecteur implémente également Debug). On n'oubliera pas qu'on peut passer d'une variable String à sa représentation &str en utilisant la méthode as_str().

On se souviendra qu'il est possible d'indiquer le type de résultat voulu à la méthode collect() de deux manières : soit le contexte est non-ambigu et permet de déterminer le type de collection à construire, soit il est possible d'indiquer un type complet ou partiel avec l'opérateur turbofish ::<> :

fn main() {
  // Utilisation d'un contexte totalement explicite
  let v: Vec<u32> = (1..=10).collect();
  println!("{v:?}");
  
  // Utilisation d'un contexte partiellement explicite
  let v: Vec<_> = (1u32..=10).collect();
  println!("{v:?}");

  // Utilisation du turbofish
  let v = (1u32..=10).collect::<Vec<_>>();
  println!("{v:?}");
}

On peut bien sûr préciser des types plus complexes, comme Vec<Result<_, _>>. Voici un exemple d'utilisation de notre programme :

$ cargo run -- johndoe:complex:password johndoe johndoe:password
[
    Ok(
        Account {
            login: "johndoe",
            password: "complex:password",
        },
    ),
    Err(
        NoColon,
    ),
    Ok(
        Account {
            login: "johndoe",
            password: "password",
        },
    ),
]

On pourra noter l'utilisation de -- sur la ligne de commande de cargo run pour séparer les arguments passés à cargo run de ceux passés à l'application. Ce n'est pas obligatoire ici mais le devient dès lors qu'on voudra passer des options commençant par - à notre application.

  1. Si vous ne l'avez pas fait précédemment, utilisez le fait que collect() appliqué à un itérateur sur des valeurs de type Result<T, E> peut produire un Result<Collection<T>, E>Collection est tout type de collection comme Vec. Ce collecteur s'arrête et renvoie une erreur dès lors qu'il en rencontre une dans l'itérateur. Dans ce cas, utilisez ? pour quitter main() avec l'erreur renvoyée, et sinon affichez les comptes utilisateur décodés.
$ cargo run -- johndoe:complex:password johndoe johndoe:password
Error: NoColon
$ cargo run -- johndoe:complex:password johndoe:password
[
    Account {
        login: "johndoe",
        password: "complex:password",
    },
    Account {
        login: "johndoe",
        password: "password",
    },
]