Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Odczytywanie pliku

Teraz dodamy funkcjonalność do odczytywania pliku określonego w argumencie file_path. Najpierw potrzebujemy przykładowego pliku do przetestowania: użyjemy pliku z niewielką ilością tekstu na wielu liniach z kilkoma powtórzonymi słowami. Listing 12-3 zawiera wiersz Emily Dickinson, który będzie dobrze działał! Utwórz plik o nazwie poem.txt w katalogu głównym projektu i wprowadź wiersz „I’m Nobody! Who are you?”

I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

Gdy tekst jest już na miejscu, edytuj src/main.rs i dodaj kod do odczytywania pliku, jak pokazano w Listingu 12-4.

use std::env;
use std::fs;

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

    let query = &args[1];
    let file_path = &args[2];

    println!("Searching for {query}");
    println!("In file {file_path}");

    let contents = fs::read_to_string(file_path)
        .expect("Should have been able to read the file");

    println!("With text:\n{contents}");
}

Najpierw wprowadzamy do zasięgu odpowiednią część biblioteki standardowej za pomocą instrukcji use: Potrzebujemy std::fs do obsługi plików.

W main nowa instrukcja fs::read_to_string przyjmuje file_path, otwiera ten plik i zwraca wartość typu std::io::Result<String>, która zawiera zawartość pliku.

Następnie ponownie dodajemy tymczasową instrukcję println!, która wypisuje wartość contents po odczytaniu pliku, abyśmy mogli sprawdzić, czy program działa do tej pory.

Uruchommy ten kod z dowolnym ciągiem znaków jako pierwszym argumentem wiersza poleceń (ponieważ nie zaimplementowaliśmy jeszcze części wyszukiwania) i plikiem poem.txt jako drugim argumentem:

$ cargo run -- the poem.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep the poem.txt`
Searching for the
In file poem.txt
With text:
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

Świetnie! Kod odczytał, a następnie wypisał zawartość pliku. Ale kod ma kilka wad. W tej chwili funkcja main pełni wiele ról: parsuje argumenty i odczytuje pliki. W miarę rozrostu programu, liczba oddzielnych zadań obsługiwanych przez funkcję main będzie wzrastać. W miarę zwiększania odpowiedzialności funkcji, staje się ona trudniejsza do zrozumienia, trudniejsza do testowania i trudniejsza do zmiany bez uszkodzenia jednej z jej części. Najlepiej jest rozdzielić funkcjonalność tak, aby każda funkcja była odpowiedzialna tylko za jedno zadanie.

Ten problem wiąże się również z drugim problemem: chociaż query i file_path są zmiennymi konfiguracyjnymi naszego programu, zmienne takie jak contents są używane do wykonywania logiki programu. Im dłuższy staje się main, tym więcej zmiennych będziemy musieli wprowadzić do zasięgu; im więcej zmiennych mamy w zasięgu, tym trudniej będzie śledzić cel każdej z nich. Najlepiej jest zgrupować zmienne konfiguracyjne w jedną strukturę, aby ich cel był jasny.

Trzeci problem polega na tym, że użyliśmy expect do wypisania komunikatu o błędzie, gdy odczyt pliku się nie powiedzie, ale komunikat o błędzie wypisuje tylko Should have been able to read the file. Odczyt pliku może zakończyć się niepowodzeniem na wiele sposobów: na przykład, plik może brakować, lub możemy nie mieć uprawnień do jego otwarcia. W tej chwili, niezależnie od sytuacji, wypisywalibyśmy ten sam komunikat o błędzie dla wszystkiego, co nie dałoby użytkownikowi żadnych informacji!

Po czwarte, używamy expect do obsługi błędu, a jeśli użytkownik uruchomi nasz program bez określenia wystarczającej liczby argumentów, otrzyma błąd index out of bounds z Rust, który nie wyjaśnia jasno problemu. Najlepiej byłoby, gdyby cały kod obsługi błędów znajdował się w jednym miejscu, tak aby przyszli konserwatorzy mieli tylko jedno miejsce do konsultowania kodu, jeśli logika obsługi błędów wymagała zmiany. Posiadanie całego kodu obsługi błędów w jednym miejscu zapewni również, że będziemy wypisywać komunikaty, które będą zrozumiałe dla naszych użytkowników końcowych.

Zajmiemy się tymi czterema problemami, refaktoryzując nasz projekt.