Nogmaals data-analyse, The way of Rust
Arnout van Kempen over rommelen in een digitale wereld.
Eerder liet ik zien hoe het data-analyse programmaatje, dat we in C schreven, ook in Rust kan werken. Dat was dus Rust, op de C-manier. Echt Rust programmeren als een Rustacean betekent dat je maximaal gebruikmaakt van de mogelijkheden van Rust. En dat vraagt soms om een andere manier van denken dan in C gebruikelijk is.
Een goede manier om te leren kan zijn je code aan ervaren programmeurs laten zien en vragen om kritiek. Op het officiële Rust-forum heb ik zo mijn eerdere code geplaatst en suggesties terug gekregen die een heel wat fraaiere uitwerking gaven. Meer idiomatic Rust, zoals men dat noemt in de Rust-gemeenschap.
De code staat natuurlijk weer op GitHub. Als je deze compileert, krijg je een programma dat ik analyse heb genoemd. Als je dat wilt proberen met het bestandje test.txt dat ook op GitHub staat, zal je zien dat de werking een heel klein beetje anders is geworden. Waar je in de eerste versie de te tellen letter en vervolgens het bestand moest aangeven, dus zo:
analyse q test.txt
maakt deze versie gebruik van opties zoals die in Windows en Unix gebruikelijk zijn en van redirection. In deze versie krijg je dus hetzelfde effect met
analyse -c q < test.txt
De hele foutafhandeling die in de vorige versie zat, wordt nu overgelaten aan Rust en aan het operating system, dus Linux, MacOS of Windows.
Maar nu het programma zelf. Daar zit aardig wat nieuws in, dus dat verdeel ik over een paar afleveringen. Eerst de volledige code zelf:
use clap::Parser;
use std::io::{self, BufRead, BufReader};
#[derive(Parser, Debug, Clone)]
struct Args {
#[clap(short, long, default_value = “x”)]
ch: char,
}
fn main() -> io::Result<()> {
let args = Args::parse();
let search_char = args.ch.to_lowercase().next().unwrap();
let r = BufReader::new(io::stdin());
let n = r.lines().try_fold(0, |partial, line| -> io::Result<_> {
let count = line?
.chars()
.filter(|&c| c.to_lowercase().next().unwrap() == search_char)
.count();
Ok(partial + count)
})?;
println!(“\n Het karakter {} komt {n} maal voor.\n”, args.ch);
Ok(())
}
Een belangrijk nieuw gegeven hier is het gebruik van de code van anderen. Zoals we in C met .h en .o files headers konden invoegen door de .h in een #include<> op te nemen en de .o tijdens compileren toe te voegen, kunnen we ook in Rust gebruik maken van externe functie-bibliotheken. Een dergelijke bibliotheek heet een crate en kan worden gepubliceerd op crates.io . Hier zijn intussen ruim 130.000 bibliotheken te vinden en als je weet wat je zoekt, kan je jezelf een hoop werk besparen.
In ons programma gebruiken we de clap-crate. Clap staat voor Command Line Argument Parser en levert met een heel simpele code de complete functionaliteit van het gebruik van argumenten op de Unix-manier. Dat betekent gebruik van korte en/of lange vlaggen, foutafhandeling met redelijk duidelijke omschrijvingen voor de gebruiker, en een automatische help-pagina die kan worden opgeroepen met de vlag -h (kort) of —help (lang).
Om een dergelijke crate te kunnen gebruiken zal je wel aan de compiler, of eigenlijk de builder/linker, moeten vertellen dat je dat wilt. Zoals je in C via gcc of via je Makefile de object-file moest aangeven, en via #include<> de header-file, zo moet je beiden ook in Rust aangeven. Alleen werkt dat iets simpeler, vooral als je nog versiebeheer nodig hebt. Rust regelt dat laatste voor je.
Alle in te voegen crates kan je vermelden in de Cargo.toml file die cargo automatisch gemaakt heeft toen je cargo new projectnaam deed. Open deze toml-file met een gewone editor, en je ziet aan het eind een regel [dependencies]. Op de regel daaronder zet je de benodigde crates, met een versie-nummer en eventueel nog extra aanwijzingen voor cargo. Nu hebben we nodig:
clap = { version = “4.4.10”, features = [“derive”]}
In de code moet vervolgens de crate in scope worden gebracht, met
use clap::Parser;
Hiermee kan de parser worden gebruikt. Vervolgens moet een struct worden gedefinieerd om de argumenten die op de CLI zijn meegegeven in te verwerken. Aan die struct koppelen we de benodigde functionaliteit uit clap met de attribuutmacro #[derive(Clone, Debug, Parser)] en in de struct nemen we veldnamen op die gelijk zijn aan de vlaggen die straks gebruikt kunnen worden in de CLI. Opnieuw gebruiken we een attribuutmacro om clap te vertellen wat we precies willen: een korte versie, -c, een lange versie, —ch, en een defaultwaarde als de gebruiker niets opgeeft. Vervolgens definiëren we velden voor alle te gebruiken vlaggen. In dit geval alleen een char. Clap doet nu de rest. In het programma kunnen we de opgegeven letter nu eenvoudig vinden met let args = Args::parse();
Omdat we geen onderscheid willen maken tussen hoofdletters en kleine letters, zetten we alles om naar kleine letters. Het is mogelijk dat hierbij sprake is van codes die langer zijn dan één karakter, bijvoorbeeld internationale tekens, en Rust verwerkt dit dan ook als een iteratie die afgelopen moet worden. Het te zoeken karakter wordt gevonden met let search_char = args.ch.to_lowercase().next().unwrap();
De volgende keer het gebruik van io en de teller die per regel telt hoe vaak het gezochte karakter voorkomt.
Wie mee wil doen met #klooienmetcomputers kan dat doen via GitHub. Maak een account op github.com en zoek naar Abmvk/kmc. Het account Abmvk volgen kan ook. Lezers zijn vrij te gebruiken wat ze willen en om zelf zaken toe te voegen of aan te passen, vragen te stellen of commentaar te leveren.
Gerelateerd
Over bits & bytes
Arnout van Kempen over rommelen in een digitale wereld.
En arrays dan?
Arnout van Kempen over rommelen in een digitale wereld.
Typecasting in COBOL
Arnout van Kempen over rommelen in een digitale wereld.
Gewone variabelen
Arnout van Kempen over rommelen in een digitale wereld.
Bestanden in soorten en maten
Arnout van Kempen over rommelen in een digitale wereld.