#Klooienmetcomputers

Data-analyse, maar nu in Rust

Arnout van Kempen over rommelen in een digitale wereld.

In de aflevering Data-analyse! (september 2023) hebben we bekeken hoe je argumenten aan een programma mee kan geven en hoe je bestanden kan openen. Hoe ziet dat er uit in Rust? Best vergelijkbaar.

Laten we eens zien of we hetzelfde programma kunnen maken. De opgave was toen:

Stel dat we van een tekst willen weten hoe vaak een opgegeven letter in die tekst voorkomt. De tekst is opgeslagen in een bestand, maar we weten nog niet welke letter we gaan tellen en ook niet in welk bestand die letter moet worden geteld. Om het nog wat lastiger te maken, tellen we hoofdletters en kleine letters mee. Verder willen we graag de tekst kunnen meelezen met de computer, ofwel het bestand moet in beeld worden gebracht. En dan graag een leesbare pagina tegelijk. Of, alternatief, de output moet naar een printer worden gestuurd, zodat we alles nog eens op papier kunnen nalezen.

We noemen het programma rust_analyse. Dat is simpel, met de opdracht cargo new rust_analyse is dat geregeld. Ook piping, wat we in het C programma hebben gebruikt, is simpel, want het werkt hetzelfde in Rust als in C.
Waar C en Rust gaan verschillen is het gebruik van argumenten. In C deden we dat door de hoofdfunctie te definiëren als

int main(int argc, char *argv[]) 

In Rust werkt dat als volgt:

use std::env;

fn main() {
   let args: Vec<String> = env::args().collect();

De functie args() uit de module std::env retourneert de argumenten van de commandline, en de methode .collect() zet die om naar een vector van strings, met daarin de argumenten.

Vervolgens moeten we testen of de opgegeven argumenten bruikbaar zijn. Voor de bestandsnaam is dat niet zo spannend, dat merken we wel zodra we het bestand willen openen. Belangrijker is de letter. We kunnen met assert! prima vaststellen of we exact drie argumenten hebben (programmanaam, bestandsnaam, letter), of het laatste argument 1 letter is en of dit een leesbare ascii-code is. Vervolgens kunnen we die converteren naar een hoofdletter, zodat we kunnen zoeken op zowel kleine letters, als hoofdletters:

assert!(args.len() == 3 && args[2].len() == 1, “\nGebruik {} <bestandsnaam> <letter>\n”, args[0]);

let letter = args[2].chars().next().unwrap();

assert!(letter.is_ascii_alphabetic(), “\nGebruik {} <bestandsnaam> <letter>, andere tekens zijn niet toegestaan\n”, args[0]);

let letter = letter.to_ascii_uppercase();

Hiermee hebben we met zekerheid een te zoeken hoofdletter in letter en wordt het tijd het bestand dat we willen doorzoeken te openen. Omdat args[1] niet direct leesbaar is als bestandsnaam, hebben we een tussenstapje nodig:

let mut bestand = File::open(args[1].as_str())?;

Om dit allemaal te laten werken hebben we nog een paar uses nodig:

use std::fs::File;
use std::io::{self, Read};

En we moeten main een returnwaarde geven, waarmee het besturingssysteem fouten met het openen van het bestand kan oplossen:

fn main() -> io::Result<()> {

Nu is het enige dat nog rest het lezen van karakters uit het bestand en tellen hoe vaak onze letter voorkomt. Dat doen we door de methode .read_exact() te gebruiken. Hiermee wordt exact het aantal bytes gelezen dat in een vooraf gedefinieerde buffer past. Die buffer is altijd een array. Omdat we karakter voor karakter gaan lezen en vergelijken hebben we dus een buffer van 1 byte nodig. Vervolgens hebben we een teller nodig. Dus

let mut buffer = [0u8; 1];
let mut teller = 0;

En dan gaan we byte voor byte lezen, zolang het goed gaat, met

while bestand.read_exact(&mut buffer).is_ok() {

En dan steeds hetzelfde: karakter uit de buffer halen en vergelijken met de opgegeven letter, beiden als hoofdletter zodat hoofd- of kleine letter niet uitmaakt en vervolgens op het scherm zetten:

let karakter = buffer[0] as char;
if karakter.to_ascii_uppercase() == letter.to_ascii_uppercase() {
    teller += 1;
    }
print!("{}", karakter);

En ten slotte het resultaat weergeven

println!("\n De letter {} kwam {} keer voor in {}, in hoofd- of kleine letter\n", letter, teller, args[1]);

Omdat we het programma een returnwaarde met een eventuele foutmelding voor het O/S hebben gegeven, moeten we nog afsluiten met een waarde om aan te geven dat alles goed is gegaan:

Ok(())

Op GitHub staat natuurlijk weer het hele programma en hetzelfde test-bestand dat we al in de aflevering over data-analyse met C gebruikten.

Is Rust nu heel anders dan C hier? Nee, op zich niet. Wie de code in C en in Rust naast elkaar legt, herkent de overeenkomstige structuur. De snelheid van beide programma’s is vergelijkbaar, de output en ook de lengte en complexiteit van beide programma’s is volstrekt vergelijkbaar. En toch is in dit simpele voorbeeld al wel een verschil zichtbaar. C laat nogal compacte, maar ook wat cryptische formuleringen toe, bijvoorbeeld:

if((teken>90 ? teken-32 : teken)==letter) teller++;

In Rust wordt dat

if karakter.to_ascii_uppercase() == letter.to_ascii_uppercase() {
    teller += 1;
}

Waar we in C zelf op een nette manier wat foutafhandeling moesten doen met

fp=fopen(argv[1], “r”);
if(fp==NULL)
{
    printf(“Kan %s niet openen\n”, argv[1]);
    return 1;
}

laten we dat in Rust zorgeloos over aan het besturingssysteem met

let mut bestand = File::open(args[1].as_str())?;

Het zijn geen grote verschillen, Rust heeft de voordelen van C, maar ondersteunt net iets beter in het schrijven van leesbare en foutloze code.

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.

Arnout van Kempen di CCO CISA is Senior manager Risk & Compliance bij Baker Tilly. Hij schrijft op persoonlijke titel. Hij is lid van de Commissie Financiƫle verslaggeving & Accountancy van de AFM en lid van de signaleringsraad van de NBA. Daarnaast is hij diaken van het bisdom 's-Hertogenbosch.

Gerelateerd

reacties

Reageer op dit artikel

Spelregels debat

    Aanmelden nieuwsbrief

    Ontvang elke werkdag (maandag t/m vrijdag) de laatste nieuwsberichten, opinies en artikelen in uw mailbox.

    Bent u NBA-lid? Dan kunt u zich ook aanmelden via uw ledenprofiel op MijnNBA.nl.