I programmeringsprosjekter som krever at programvaren skal kjøres på flere operativsystemer, oppstår ofte behovet for å håndtere plattformspesifikke filnavn og andre systemspesifikke forskjeller. Rust tilbyr flere måter å tilpasse programlogikk på tvers av plattformer, og i dette eksemplet skal vi utforske hvordan man kan bruke betinget kompilering til å håndtere slike situasjoner, spesielt når man jobber med filnavn og kataloger.
I et typisk testscenario kan det være nødvendig å lage forskjellige versjoner av utdatafilene avhengig av om programmet kjører på Windows eller Unix-baserte systemer. I katalogen tests/expected vil for eksempel filene for testen name_a ha to versjoner: én for Unix og én for Windows:
Testen name_a kan se slik ut i Rust:
Funksjonen run bruker en annen funksjon, format_file_name, til å generere det riktige filnavnet. Denne funksjonen benytter betinget kompilering for å avgjøre hvilken versjon som skal kompilere basert på plattformen. Hvis programmet kompilert på Windows, vil følgende versjon av funksjonen bli brukt for å legge til .windows til filnavnet:
For Unix-baserte systemer, derimot, vil funksjonen bruke det opprinnelige filnavnet uten endringer:
Ved å bruke std::borrow::Cow, unngår man unødvendige kloning av strenger på Unix-systemer, samtidig som man kan returnere et eierskapsbasert strengenavn på Windows.
En annen test som bare skal kjøres på Unix-plattformer, er unreadable_dir, som demonstrerer hvordan man kan håndtere filer og kataloger med spesifikke rettigheter. Denne testen oppretter en katalog med lesetilgang og setter tillatelsene til 000, som gjør at katalogen ikke kan leses eller skrives til. Deretter kjøres et kommando med findr for å bekrefte at programmet ikke feiler:
I denne testen opprettes en katalog som ikke kan leses (på en ikke-Windows plattform) og verifiserer at programmet håndterer en slik feilsituasjon riktig. Kommandoen chmod setter tillatelsene for katalogen til 000, og findr skal bekrefte at det ikke feiler under søket.
Når man utvikler slike systemverktøy som find eller tree, er det flere funksjoner og alternativer man kan implementere for å gjøre programmet mer nyttig. For eksempel kan man legge til funksjonalitet for å kontrollere hvor dypt i katalogstrukturen programmet skal søke, ved hjelp av opsjonene -max_depth og -min_depth. Rust-biblioteket WalkDir tilbyr slike alternativer som kan utnyttes:
Et annet vanlig scenario for programmer som søker i filsystemet, er å finne filer basert på størrelse. find-kommandoen har støtte for å finne filer som er større enn, mindre enn eller nøyaktig lik en spesifisert størrelse, for eksempel ved å bruke -size:
Videre kan man legge til muligheten til å slette filer, for eksempel ved å bruke -delete-flagget til å fjerne tomme filer. Dette er en nyttig funksjon som kan være verdifull å implementere for å fjerne overflødige filer:
Et annet nyttig tillegg kan være å implementere en funksjon som teller antall resultater som er funnet, som den gjør i uniq -c:
Slik funksjonalitet kan implementeres i programmet for å gi mer informasjon om resultatene, for eksempel hvor mange tomme filer som finnes.
Å lage et Rust-basert alternativ til tree-kommandoen kan også være et nyttig prosjekt. Denne kommandoen lar brukeren se en visuell fremstilling av fil- og katalogstrukturen, og kan ha flere alternativer for å tilpasse utskriften, for eksempel ved å vise kun kataloger:
En annen interessant funksjon som kan legges til, er muligheten til å bruke et filmønster for å vise spesifikke filer, slik som tree -P *.csv.
Til slutt kan man også sammenligne sitt eget program med eksisterende løsninger som fd, som er en Rust-basert erstatning for find, og se hvordan andre har løst de samme utfordringene.
Det er viktig å merke seg at slike programmer som find kan bli ganske komplekse når man begynner å kombinere flere kriterier for å finne filer basert på størrelse, tillatelser, datoer og andre attributter. Det er derfor viktig å fokusere på modulær utvikling og testing, for å sikre at programmet fungerer på tvers av forskjellige plattformer og håndterer alle edge-caser på en robust måte.
Hvordan håndtere filbehandling og regulære uttrykk i Rust
I utvikling av effektive filbehandlingsverktøy i Rust, er det flere viktige konsepter og teknikker som kommer til å spille en stor rolle. Denne artikkelen tar for seg håndtering av filer, bruk av regulære uttrykk og bitvise operatorer for å søke gjennom innholdet i filer. Eksempelet som presenteres er delt inn i funksjoner som håndterer filsystemet, finner linjer som matcher et spesifikt mønster, og skriver ut resultatene i ønsket format.
En grunnleggende funksjon i Rust-programmet som vi ser på, er et system som itererer over et sett med filstier og søker etter bestemte mønstre i innholdet. Her benytter vi oss av funksjoner som tar hensyn til både feilhåndtering og effektivitet når vi jobber med store mengder data. La oss først gå gjennom hvordan filbehandling kan håndteres med de nødvendige Rust-funksjonene.
Det hele starter med å initialisere en tom vektor som skal inneholde resultatene av søket. Programmet itererer over de gitte filstiene og forsøker å hente metadata for hver fil. Ved å sjekke om en sti peker på en katalog, kan vi bestemme om vi skal gå rekursivt inn i undermapper og inkludere alle filene i denne katalogen i søkeresultatene. Den rekursive søkingen benytter Iterator::flatten, som filtrerer bort feil og bare beholder de gyldige resultatene. Feil som oppstår under søking, for eksempel om en fil ikke finnes, blir også håndtert, og det gis tilbakemelding til brukeren.
Når det gjelder selve filbehandlingen, har Rust flere verktøy som lar oss søke effektivt etter mønstre. I eksempelet benytter vi find_lines-funksjonen for å finne linjer som matcher et gitt regulært uttrykk. Denne funksjonen håndterer linjeforlesning, og vi bruker bitvis eksklusiv OR (XOR) for å avgjøre om en linje skal inkluderes basert på brukerens preferanser, som om de ønsker å invertisere mønsteret. Bruken av std::mem::take gjør det mulig å overføre eierforholdet til linjen uten å lage en ekstra kopi, noe som bidrar til bedre ytelse.
En annen viktig teknikk som demonstreres her, er bruken av RegexBuilder for å bygge mer avanserte regulære uttrykk. For eksempel kan vi lage mønstre som er uavhengige av store og små bokstaver ved å bruke case_insensitive-alternativet. Rusts regex-motor er ikke fullstendig kompatibel med PCRE (Perl Compatible Regular Expressions), og derfor finnes det visse funksjoner som ikke støttes, som bakreferanser og look-around-uttrykk. Dette er noe å ta hensyn til når man jobber med regex i Rust.
Programmet benytter seg også av en praktisk "closure"-funksjon for utskrift. Denne funksjonen tar hensyn til antall inputfiler og bestemmer om filnavnene skal vises før hver linje som er funnet. Hvis det er mer enn én fil, vises filnavnet før selve innholdet. På denne måten kan programmet håndtere flere filer samtidig og presentere resultatene på en lesbar måte.
Et annet sentralt poeng er hvordan programmet håndterer potensielle feil. Når en fil ikke kan åpnes, eller når det oppstår problemer med å finne linjer som matcher mønsteret, gir programmet tilbakemelding til standardfeilstrømmen (STDERR). Dette er en viktig funksjon, ettersom den sørger for at brukeren får beskjed om eventuelle problemer som kan oppstå under kjøringen.
Videre kan man forbedre programmet ved å implementere funksjoner som fremhever de matchende tekstene, slik som ripgrep-verktøyet gjør. Dette kan oppnås ved hjelp av funksjonen Regex::find for å finne start- og sluttposisjonene til det matchede mønsteret og ved å bruke farger for å markere treffene i terminalen. Dette vil øke brukervennligheten og gjøre det lettere å identifisere relevante treff i store tekstmengder.
Det er også interessant å merke seg at det i kapittelet diskuteres hvordan man kan utvide ferdighetene fra tidligere kapitler, spesielt når det gjelder rekursiv filhåndtering og regulære uttrykk. Rust tilbyr et sett med verktøy som gjør det mulig å kombinere flere konsepter for å lage kraftige søke- og filbehandlingsapplikasjoner. For eksempel kan man bruke BufRead til å lese filene linje for linje på en effektiv måte, og std::cmp::Ordering for å sammenligne strenger, noe som kan være nyttig når man arbeider med sorterte filer.
En viktig del av Rusts designfilosofi er effektiv ressursbruk og ytelse, og dette reflekteres i måten vi håndterer data og feil. I Rust får man kontroll over minnehåndtering, og ved å bruke teknikker som take for å overføre eierskap av data uten å kopiere dem, kan vi minimere unødvendige operasjoner og oppnå raskere behandling.
Når man bygger slike verktøy, er det essensielt å forstå hvordan man kan balansere mellom kompleksitet og ytelse. Med Rusts strenge typekontroll og minnehåndtering får man et kraftig verktøy for å utvikle programmer som ikke bare er raske, men også pålitelige og enkle å vedlikeholde. Når man forstår de grunnleggende teknikkene som beskrevet her, vil man være godt rustet til å bygge mer komplekse applikasjoner og verktøy som kan håndtere store datamengder og gjøre effektive søk gjennom filer.
Hvordan bygge et program for å velge og vise tilfeldige sitater eller tekster
Et program som bruker tilfeldigheter kan være både spennende og utfordrende å utvikle. Den grunnleggende ideen i slike programmer er å bruke en form for pseudotilfeldig tallgenerator (PRNG) for å hente et element fra en samling, enten det er en liste, en fil eller et annet datalager. Et godt eksempel på dette kan være et program som trekker et tilfeldig "fortune" – et kort sitat eller tekst – fra en samling av slike tekster lagret i filer på datamaskinen. For å implementere dette, er det flere nøkkeltrinn som må følges for å sikre at programmet fungerer på en pålitelig og effektiv måte.
Først, for å finne de riktige filene, bruker programmet et bibliotek som walkdir::WalkDir for å traversere filsystemet. Dette tillater programmet å finne alle filene i et angitt katalog, og det ignorerer eventuelle feil for uleste filer eller kataloger. Programmet samler kun de filene som er vanlige (og som ikke har .dat-utvidelse), og sorterer dem i stigende rekkefølge, før det fjerner eventuelle duplikater.
Deretter leses innholdet fra disse filene. Hver fil behandles linje for linje, og programmet ser etter spesifikke markører – i dette tilfellet prosenttegn (%) – som indikerer slutten på et sitat. Når et slikt tegn oppdages, lagres teksten i en buffer og legges til listen over sitater, før bufferet tømmes for neste setning. Hvis programmet støter på et feil i filen, vil det gi en nyttig feilmelding som forklarer hvor og hvorfor feilen oppstod.
Neste steg er å implementere funksjonen for å hente et tilfeldig "fortune". Denne funksjonen bruker en tilfeldig generator som kan enten ta et forhåndsbestemt frø (sånn at resultatet kan reproduseres) eller benytte systemets egen tilfeldige generator. Ved å bruke funksjonen choose fra biblioteket rand, kan programmet velge et tilfeldig sitat fra listen av tilgjengelige alternativer.
I tillegg til å velge et tilfeldig sitat, kan programmet også implementere muligheten for å filtrere sitater etter et mønster. Ved hjelp av regulære uttrykk kan brukeren spesifisere et bestemt mønster, og programmet vil kun vise sitater som matcher dette mønsteret. For eksempel kan man filtrere ut alle sitater som inneholder bestemte ord eller fraser, noe som gir brukeren mer kontroll over innholdet.
Men det er også noen utfordringer ved denne tilnærmingen. Et av de største problemene kan være at sitatene som er lagret i filene, kan inneholde linjeskift, som gjør at de ikke nødvendigvis vil matche det regulære uttrykket på en enkel måte. Denne utfordringen kan overvinnes ved å preprosesserere tekstene, f.eks. ved å fjerne eller håndtere linjeskift før man prøver å matche dem med et mønster.
En annen viktig faktor er å tenke på hvordan man håndterer forskjellige typer feilsituasjoner. Hva skjer hvis filen ikke kan åpnes? Hva om innholdet i filene ikke følger det forventede formatet? Hvordan håndterer programmet slike tilfeller på en måte som fortsatt gir brukeren en god opplevelse?
Videre kan programmet utvides med flere funksjoner, som å legge til alternativet -n for å begrense sitatene til en viss lengde, eller alternativet -s som kun velger korte sitater. En annen mulighet er å bruke en ordbokfil, som f.eks. /usr/share/dict/words, som kan inneholde tusenvis av engelske ord og setninger, og bruke dette som en kilde til tilfeldige sitater.
I tillegg til den grunnleggende implementasjonen kan man også eksperimentere med andre typer tilfeldige spill eller programmer som involverer tilfeldigheter. For eksempel kan man lage et program der brukeren må gjette et tilfeldig valgt tall i et bestemt intervall, eller kanskje et mer kompleks spill som "Wheel of Fortune", hvor brukeren gjetter bokstaver i et tilfeldig valgt ord eller frase.
Så til tross for at det kan virke enkelt ved første øyekast, finnes det mange aspekter ved programmering som involverer tilfeldigheter som krever grundig gjennomtenkning. Det er ikke bare teknisk utfordrende, men også en mulighet for kreativ utfoldelse. De samme prinsippene kan brukes til en rekke forskjellige applikasjoner, og åpner for spennende muligheter i spillutvikling, tekstgenerering og mer.

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский