V2 programmerer i Rust: Vi møder den første boss og undslipper med nød og næppe

20 kommentarer.  Hop til debatten
V2 programmerer i Rust: Vi møder den første boss og undslipper med nød og næppe
Illustration: Pixabay.com-bruger terimakasih0.
Den frygtede lånetjekker stikker sit fæle fjæs frem i vores Rust-eventyr, der skal ende med et lille, hurtigt og stabilt program. Og så brokker vi os en hel masse.
7. maj 2020 kl. 05:04
errorÆldre end 30 dage
Manglende links i teksten kan sandsynligvis findes i bunden af artiklen.

Et stykke tid tilbage skrev vi et lille program i Java, der løste et problem med at udtrække tekstfelter fra en stor bunke PDF-filer.

Vi fik compilet og testet med continuous integration på Github, men var ikke helt tilfredse med det bøvl, som Java-installation giver ikke-tekniske brugere.

Så compilede vi Java-koden til hjemmefødt maskinkode, og var utilfredse med størrelsen på 8 megabyte af det færdige program.

Log ind og få adgang
Du kan læse indholdet ved at logge ind eller oprette dig som ny bruger.
20 kommentarer.  Hop til debatten
Debatten
Log ind for at deltage i debatten.
settingsDebatindstillinger
19
13. maj 2020 kl. 13:55

Enig i Troels og Jespers fine svar og dine efterfølgende konklusioner.

Hvad angår exceptions versus Result<T, E>: Der er faktisk en igangværende diskussion om man skulle "bage" Result-håndtering yderligere ind i Rust-sproget selv, eksempelvis på withoutboats' blog: https://boats.gitlab.io/blog/post/failure-to-fehler/ og https://boats.gitlab.io/blog/post/why-ok-wrapping/

Det er en spændende diskussion, men til lavniveaukode synes jeg faktisk rigtig godt om den helt eksplicitte håndtering af programforløb i den enkelte funktion. Men det er måske en smags- og tilvænningssag (jeg bruger kun Rust til hobbyprojekter)


Desuden er et Directory i posix anset som en fil.

Yep, det var derfor jeg meget skrev "på tværs af platforme".

Rusts standardbibliotek er rimelig omhyggeligt med at ikke love noget som det ikke holde på tværs af de understøttede platforme, eksempelvis er permissions ikke ens på tværs af platforme: https://doc.rust-lang.org/std/fs/struct.Permissions.html

18
13. maj 2020 kl. 12:49

Hej Jesper

Jeg er lidt i tvivl om du fik svar på din undren eller jeg fik besvaret noget helt andet.

I min oprindelige kommentar spørger jeg:

Hvordan kan det være at du skal hente "file" igen i denne løkke? Burde den exception handling ikke være inkluderet i " fs::read_dir(dir)?" .... ?

Jeg undrer mig over hvorfor der er exception handling både på read_dir og på de enkelte entries. Med andre ord, hvorfor skulle read_dir returnere en iterator over intries hvor nogle skulle kunne være ikke-valide?

Men til at starte med havde jeg ikke forståelse for hvordan exception handling fungerede i Rust. Jeg troede det var lidt ligesom i Java hvor en function rapportere hvilke exceptions der kan kastes. Men det er ikke tilfældet. I stedet returneres et Result<T, E> datatype som skal håndteres.

17
13. maj 2020 kl. 06:53

Jeg er lidt i tvivl om du fik svar på din undren eller jeg fik besvaret noget helt andet.

Hvis det sidste er tilfældet er jeg nysgerrig på hvad du egentlig er i tvivl om. :-D

14
12. maj 2020 kl. 11:15

Så hvis man skulle gøre det lidt mere verbose kunne man skrive.

  1. for entry_result in fs::read_dir(dir)? {
  2.  
  3. let file = entry_result?;
  4. let pathbuf = file.path();
  5.  
  6. println!("{}", pathbuf.display());
  7.  
  8. if pathbuf.extension().unwrap_or(OsStr::new("")) == "txt" {
  9. println!("Fandt en tekstfil: {}", pathbuf.display());
  10. files_in_dir.push(pathbuf);
  11. }
  12. }

Jeg er dog lidt i tvivl om hvorfor fs::read_dir(dir)? ikke skulle have valide entries. Som derfor kræver en exception handling.

13
12. maj 2020 kl. 11:06

Du er nødt til at pakke resultatet ud i Rust før du kan gøre noget med det, og inde i en funktion der selv returnerer Result<T, E> kan du gøre dette med ?

Aha. Det er jo meget smart.
Så hvis jeg selv skal svare mit oprindelige spørgsmål:
"fs::read_dir(dir)?" returnerer en itterator eller en fejl. Hvis det er en fejl så håndteres det i "fn main() -> io::Result<()> {".
I for løkken udtrykkes resultatet af itterator i "file" som er en "Result<T, E>" der først skal checkes for fejl. Det er lidt indforstået.
Jeg tror jeg forstår nu. phew :D

12
12. maj 2020 kl. 10:01

File er et Result<T, E>, som er en sum type, der indeholder enten T (operationen lykkedes) eller E (noget gik galt)

Du er nødt til at pakke resultatet ud i Rust før du kan gøre noget med det, og inde i en funktion der selv returnerer Result<T, E> kan du gøre dette med ? ?

11
12. maj 2020 kl. 09:39

Variabler i Rust kan redeklareres ved at bruge en ny let-binding, der respekterer scope hvis relevant: <a href="https://doc.rust-lang.org/nightly/rust-by-example/variable_bindings/sco…;
<p>?-operatoren kan bruges i en funktion der returnerer et Result, og lader funktionen fortsætte med variablen hvis Ok, eller returnerer fejlen ved Err: <a href="https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-pa…;.

Tak Troels, nu forstår jeg (tror jeg). Så i dette tilfælde er "file" en function pointer der bliver kaldt med ?-operator for derefter at blive redeklereret til en file datatype. For Rust programmører er det indforstået at "file" er en function pointer, der tilmed bliver kaldt, fordi den bliver kaldt med ?-operator. umiddelbart kunne det jo ligne at "file" er en variabel. Men det er ikke tilfældet. Har jeg forstået det korrekt?

10
12. maj 2020 kl. 07:38

Variabler i Rust kan redeklareres ved at bruge en ny let-binding, der respekterer scope hvis relevant: https://doc.rust-lang.org/nightly/rust-by-example/variable_bindings/scope.html

?-operatoren kan bruges i en funktion der returnerer et Result, og lader funktionen fortsætte med variablen hvis Ok, eller returnerer fejlen ved Err: https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html

9
12. maj 2020 kl. 06:54

Av, nu er vi jo nede i de sværeste datalogiske spørgsmål

Jeg tror du misforstår mig. Jeg er ikke så bekendt med Rust, så grunden til at jeg er spørger er fordi jeg ikke kan gennemskue hvad der sker. Det virker som om at du skriver at den Entry der kommer ud af "fs::read_dir(dir)?" er en slags function pointer der kan kaldes individuelt her:

Eksempelvis POSIX og Win32 har individuelle kald til hver entry (opendir/readdir/closedir hhv FindFirstFile/FindNextFile/FindClose), så man ikke er tvunget til at have hele listen in memory.

I så fald er datatypen på file redekleret ved:

  1. let file = file?;

Hvilket er grunden til at jeg bruger ordet Redeklaration i overskriften til min oprindelige kommentar. Men har jeg forstået det korrekt?

Desuden er et Directory i posix anset som en fil.

8
11. maj 2020 kl. 20:12

Et bud med match:

[geshifilter-]for entry in fs::read_dir(dir)? { let file = entry?; let pathbuf = file.path(); match pathbuf.extension().and_then(OsStr::to_str) { Some("txt") => { txt_files_in_dir.push(pathbuf); } _ => {} } }[/geshifilter-]

7
11. maj 2020 kl. 12:25

Skulle det så ikke heller have været.

Av, nu er vi jo nede i de sværeste datalogiske spørgsmål -- hvad ting skal hedde. "file" burde måske hedde "entry", hvis skal være helt nøjagtige, for et directory indeholder jo andet end filer (set på tværs af platforme)

Selv har jeg det ambivalent med at genbruge samme variabelnavn til at smide "Result"-delen væk ("file" i Tanias kode). Det forvirrer mig lidt i forhold til andre sprog hvor man slet ikke må den slags, men til gengæld holder det jo scope-størrelsen nede, hvilket alting er en god ting i min bog, da det gør koden nemmere at læse. Og i tilfældet som det her hvor det ændrer en Result<T, E> til en T, er det fint i min bog.

Så, mit bedste bud er

for entry in fs::read_dir(dir)? {

    let entry = entry?;
    let pathbuf = entry.path();
    ...

Men det er i den sidste ende en smagssag.

6
11. maj 2020 kl. 06:14

Derfor ligger der et librarykald under hver iteration i "for file in fs::read_dir(dir?)" og det kan derfor tjekkes.

Skulle det så ikke heller have været:

  1. for file_handle in fs::read_dir(dir)? {
  2.  
  3. let file = file_handle?;
  4. let pathbuf = file.path();
  5.  
  6. println!("{}", pathbuf.display());
  7.  
  8. if pathbuf.extension().unwrap_or(OsStr::new("")) == "txt" {
  9. println!("Fandt en tekstfil: {}", pathbuf.display());
  10. files_in_dir.push(pathbuf);
  11. }
  12.  
  13. }

5
8. maj 2020 kl. 11:24

Hvordan kan det være at du skal hente "file" igen i denne løkke? Burde den exception handling ikke være inkluderet i " fs::read_dir(dir)?" .... ? :)

Eksempelvis POSIX og Win32 har individuelle kald til hver entry (opendir/readdir/closedir hhv FindFirstFile/FindNextFile/FindClose), så man ikke er tvunget til at have hele listen in memory.

Derfor ligger der et librarykald under hver iteration i "for file in fs::read_dir(dir?)" og det kan derfor tjekkes.

4
8. maj 2020 kl. 07:18

Hej Tanja,

Rigtigt fed artikkel!!

Hvordan kan det være at du skal hente "file" igen i denne løkke? Burde den exception handling ikke være inkluderet i " fs::read_dir(dir)?" .... ? :)

  1. for file in fs::read_dir(dir)? {
  2.  
  3. let file = file?;
  4. let pathbuf = file.path();
  5.  
  6. println!("{}", pathbuf.display());
  7.  
  8. if pathbuf.extension().unwrap_or(OsStr::new("")) == "txt" {
  9. println!("Fandt en tekstfil: {}", pathbuf.display());
  10. files_in_dir.push(pathbuf);
  11. }
  12.  
  13. }

3
8. maj 2020 kl. 02:00

interessant at se noget Rust-kode, selv om det virker som et meget ungt og lidt besværligt sprog, som utvivlsomt vil gennemgå mange "udglatninger" på vejen til modenhed.

Jeg synes bestemt også det er interessant ?

Hvad angår udglatninger, så har Rust nået sin version 1.0 og har et ret stærkt stability pledge: https://blog.rust-lang.org/2014/10/30/Stability.html. Angående modenhed, så er der flere software-firmaer der er begyndt at spille bold:

2
7. maj 2020 kl. 21:26

Vi bruger, som forslået

Indrømmet, det er en hård boss hvis han formår at give dig fysiske skader.

Nå, spøg til side, interessant at se noget Rust-kode, selv om det virker som et meget ungt og lidt besværligt sprog, som utvivlsomt vil gennemgå mange "udglatninger" på vejen til modenhed.

1
7. maj 2020 kl. 17:03

Rigtig god serie, Tania!

Det driller jo lidt at extension er en Option, som enten er en Some(noget) eller None.

Hvis man er ved at kløjs i udpakningen af Option vha. "unwrap_or_else" ovenfor, vil jeg anbefale følgende struktur, som (for mig) gør det mere tydeligt at der tjekkes to ting, og ikke kun én:

if let Some(extension) = pathbuf.extension() {
    if extension == "txt" {
        println!("Fandt en tekstfil: {:}",
                pathbuf.display());
        files_in_dir.push(pathbuf);
    }
}

Man kan også gøre det i én linje, men da skal man opgive den belejlige feature at man kan sammenligne en OsStr med en str:

if pathbuf.extension() == Some(OsStr::new("txt")) {
    ...
}

Man kan også bruge denne syntaks, men den er desværre stadig eksperimentel:

if let Some(ext) = pathbuf.extension() && ext =="txt" {
    println!("Fandt en tekstfil: {:}",
            pathbuf.display());
    files_in_dir.push(pathbuf);
}