Horloge

La mesure du temps dans un programme est une de ces tâches qui semble simple au premier abord, mais se révèle complexe quand on y regarde de plus près. L’évolution des APIs de gestion du temps de C++ en témoigne : parti des fonctionnalités simples du C, C++ a graduellement accumulé une API très complexe en essayant de supporter toutes les utilisations avancées de l’horloge système pour lesquelles time() et clock() se révèlent être trop simplistes.

Rust a adopté ici une approche nettement plus minimaliste : la vocation du module std::time de la bibliothèque standard Rust n’est pas de répondre à tous les besoins possibles et imaginables de gestion du temps, si obscurs soient-ils, mais de fournir juste ce qu’il faut pour…

  • Mesurer précisément le temps écoulé pendant l’exécution du programme.
  • Connaître l’heure système UTC et pouvoir la comparer entre deux processus et avec les différentes timestamps d’accès aux fichiers.

Les fonctionnalités plus avancées, comme la gestion des fuseaux horaires ou le formatage des dates/heures, sont déléguées à des bibliothèques externes spécialisées comme time et chrono.

Mesure du temps écoulé

L’équivalent Rust de la fonction clock() du C et de l’horloge steady_clock de C++ est le type Instant. C’est une horloge monotone : elle part d’un point arbitraire dans le passé, n’est pas modifiée quand l’heure système est modifiée, et on peut donc l’utiliser quand on veut savoir combien de temps prennent les opérations effectuées par le programme.

La conception est similaire à celle de std::chrono en C++, mais avec une API qui est beaucoup plus simple, tout en restant assez puissante pour tous les besoins courants :

  • A tout moment, on peut demander l’heure qu’il est du point de vue de cette horloge avec Instant::now(). Cette information est représentée par un objet de type Instant.
  • Connaissant deux Instants, on peut calculer le temps écoulé entre les deux en les soustrayant. Ce temps écoulé est représenté par un objet de type Duration.
  • De façon symétrique, on peut ajouter ou supprimer une Duration à un Instant pour obtenir un autre Instant qui représente la valeur de l’horloge attendue N secondes avant/après la mesure de temps précédemment effectuée.

Voici un exemple d’utilisation de Instant et Duration :

#![allow(unused)]
fn main() {
use std::time::{Instant, Duration};

// Exécution minutée
let debut = Instant::now();
println!("Affichage d'un texte");
let duree = debut.elapsed();

// Analyse du temps d'exécution
println!("L'affichage a pris {duree:?}");
assert!(duree < Duration::from_millis(100));  // printf n'est pas si lent !
}

Heure système UTC

Instant ne permet pas de répondre à la question utilisateur “Quelle heure est-il ?”, car son point d’origine est arbitraire : ça peut être l’allumage de l’ordinateur, le lancement du programme, le dernier redémarrage vraiment complet… c’est dépendant de l’OS qu’on est en train d’utiliser. Il y a même des divergences d’opinions entre les OS sur la pertinence de continuer à mesurer ou pas le temps écoulé quand l’ordinateur passe en veille (la bonne approche dépend du besoin).

L’horloge Rust qui permet de se raccorder au temps humain, comme la fonction time() en C et l’horloge system_clock en C++, s’appelle SystemTime. Elle s’utilise un peu comme Instant, mais elle a en plus un point de référence UNIX_EPOCH qui permet d’en déduire la date/heure UTC.

Les précautions usuelles concernant l’heure système s’appliquent :

  • Gardez à l’esprit que l’horloge système n’est pas forcément à l’heure. Il peut être dangereux de présumer qu’elle l’est si, par exemple, la sécurité de votre programme en dépend.
  • Il est peu probable que UTC soit le fuseau horaire de l’utilisateur. On doit donc éviter d’afficher des heures UTC dans des messages destinés à ce dernier, sinon il y a un risque de confusion.
  • L’horloge système est sujette à se décaler brutalement vers le passé ou l’avenir si l’utilisateur ou un système de mise à l’heure automatique change sa valeur. Ce n’est donc pas une horloge adaptée pour mesurer des durées d’exécution au sein du programme.

Voici un exemple d’utilisation de SystemTime :

#![allow(unused)]
fn main() {
use std::time::SystemTime;

let dt = SystemTime::UNIX_EPOCH
                    .elapsed()
                    .expect("Non, on n'est pas plus tôt que le 1/1/1970 !");

println!("Il s'est passé {dt:?} depuis l'epoch Unix");
}

On le voit, les fonctionnalités de manipulation de l’heure système de la bibliothèque standard Rust sont minimalistes, et on a fortement intérêt à les compléter avec des bibliothèques comme time et chrono si on a l’intention de faire quoi que ce soit d’un peu complexe comme afficher des dates/heures à l’utilisateur.

Cette conception différente s’explique par la facilité d’utilisation des bibliothèques tierce partie en Rust, via Cargo, qui évite d’encombrer la bibliothèque standard de fonctionnalités avancées au risque de réduire sa portabilité entre matériels et systèmes d’exploitation.