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: