Rust lang series episode #26 — text file reading (#rust-series)

in rust-series •  8 years ago  (edited)

Hello everyone, new episode of Rust lang series is here. As we understand more language fundamentals today we can try again something more practical. Quite common task here is to read content of some text file located somewhere on our file system for further needs and that's exactly what we are going to achieve today.

Reading text file

Read text file is file system operation and we will mostly utilize API from std::fs and std::io. Let's take a look at simple example that will read "/etc/hosts" file and print it on standard output and then break it down to make it absolutely clear.

fn main() {
    use std::fs::File;
    use std::io::Read;

    let file_path = "/etc/hosts";

    let mut data = String::new();
    let mut f = File::open(file_path).unwrap();
    f.read_to_string(&mut data);
    println!("{}", data);
    println!("Application finished!");
}

Breaking down

First we bind needed types for more comfort in further usage

std::fs::File is Rust File type, it's reference to an open file on the file system
std::io::Read is trait allowing reading bytes from source, defines read_to_string method

file_path is user defined path in file path in file system

data is String buffer that is filled with file content after read_to_string call

We assign reference to an open file with File::open(file_path) which opens it for reading. Don’t forget to unwrap reference from result.

We reads from /etc/hosts which should exist in particular system. If you have other OS, change path to any existing text file in your system.

File::read_to_string reads all bytes until EOF and place them to data buffer mutable string.
Now we can easily print string containing the file content.

# output
127.0.0.1 localhost
...
App finished!

Note when you change file_path to some not existing content your app will crash with something like this. Threrefore its often desirable to provide better error handling.

Running `target/debug/myrust asdfasd`
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { repr: Os { code: 2, message: "No such file or directory" } }', ../src/libcore/result.rs:788

Proper error handling

As previous example doesn't provide proper error handling and any I/O problem will cause application termination. As we already know how to handle errors from previous episodes it will be piece of cake for us. This is how it can be handled with matching result.

fn main() {
    use std::fs::File;
    use std::io::Read;

    let file_path = "/etc/not-exiting-file";
    let mut data = String::new();
    let mut f = File::open(file_path);

    match f {
        Ok(mut f) => {
            println! ("File exists");
            let reading_result = f.read_to_string(&mut data);
            match reading_result {
                Ok(size) => {
                    println! ("Read: {} bytes, file content:", size);
                    println!("{}", data);
                },
                Err(err) => println! ("Error while reading:{}", err.to_string())
            }
        },
        Err(f) => println! ("Error occured: {}", f.to_string())
    }
    println! ("Application finished!");
}

# output
Error occured: No such file or directory (os error 2)
Application finished!

Version with error composing

Or we can do it more elegantly with result composing

fn read_file() -> Result<String, String> {
    use std::fs::File;
    use std::io::Read;
    let file_path = "/etc/not-existing-file";
    let mut data = String::new();

    File::open(file_path)   // Result<File>
        .map_err(|err| { "Cannot find file".to_string() })
        .and_then(|mut f| {
            f.read_to_string(&mut data)
                .map_err(|err| "Cannot read file".to_string())
                .map(|_| data)
        })
}

fn main() {
    let result = read_file();
    match result {
        Ok(n) => println!(""),
        Err(err) => println!("{}", err)
    }

    println!("Application finished!")
}

# output
Cannot find file
Application finished!

Version with using try! macro

Another option is to make it in more compact way with using try! It's up to us how much detailed error handling we require for our application.

fn read_file() -> Result<String, std::io::Error> {
    use std::fs::File;
    use std::io::Read;
    let file_path = "/etc/host";
    let mut data = String::new();

    let mut file = try!(File::open(file_path));
    try!(file.read_to_string(&mut data));

    Ok(data)
}

fn main() {
    let result = read_file();
    match result {
        Ok(data) => println!("Data:{}", data),
        Err(err) => println!("Error:{}", err)
    }

    println!("Application finished!");
}

# output
Error:No such file or directory (os error 2)
Application finished!

Cool! Now you can read text files on your file system and handle potential errors and this is basically how it can be done. What way you choose it's up to you. In general more complex your application is more carefully you should handle errors but there might be some exceptions to this rule so don't take it as a dogma.

Postfix

That's all for now, thank you for your appreciations, feel free to comment and point out possible mistakes (first 24 hours works the best but any time is fine). May Jesus bless your programming skills, use them wisely and see you next time.

Meanwhile you can also check the official documentation for related information:

#rust-series
#rust-lang
#rust

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!