Handling files
Let's start with the basics. First, let's open and write into a file:
use std::fs::File; use std::io::{self, Write}; fn write_into_file(content: &str, file_name: &str) -> io::Result<()> { let mut f = File::create(file_name)?; f.write_all(content.as_bytes()) }
Now let's explain this code:
use std::fs::File;
Nothing fancy, we just import the File type:
use std::io::{self, Write};
This set of imports is more interesting: we import the io module (self) and the Write trait. For the second, if we didn't import it, we wouldn't be able to use the write_all method (because you need to import a trait to use its methods):
fn write_into_file(content: &str, file_name: &str) -> io::Result<()> {
We declared a function named write_into_file that takes a filename and the content you want to write into the file as arguments. (Note that the file will be overwritten by this content!) It returns an io::Result type. It is an alias over the normal Result type (its documentation is at https://doc.rust-lang.org/stable/std/result/enum.Result.html) declared as follows:
type Result<T> = Result<T, Error>;
The only difference is that in case of error, the error type is already defined.
I recommend that you to take a look at its documentation in case you want to go further, at https://doc.rust-lang.org/stable/std/io/type.Result.html.
So if our function worked without errors, it'll return Ok(()); it's the Ok variant containing an empty tuple which is considered the Rust equivalent of the void type. In case of error, it'll contain an io::Error, and it'll be up to you to handle it (or not). We'll come back to error handling a bit later.
Now let's look at the next line:
let mut f = File::create(file_name)?;
Here, we call the static method create of the File type. If the file exists, it'll be truncated and if it doesn't, it'll be created. More information about this method can be found at https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.create.
Now let's look at this strange ? symbol. It's a syntactic sugar over the try! macro. The try! macro is very simple to understand and its code can be resumed as this:
match result { Ok(value) => value, Err(error) => return Err(error), }
So that's pretty easy, but annoying to rewrite over and over, so the Rust teams decided to first introduce the try! macro and then after a long consensus, decided to add the ? syntactic sugar over it (it also works with the Option type). However, both code pieces are still working, so you can perfectly do as well:
use std::fs::File; use std::io::{self, Write}; fn write_into_file(content: &str, file_name: &str) ->
io::Result<()> { let mut f = try!(File::create(file_name)); f.write_all(content.as_bytes()) }
It's exactly the same. Alternatively, you can write the full version too:
use std::fs::File; use std::io::{self, Write}; fn write_into_file(content: &str, file_name: &str) -> io::Result<()>
{ let mut f = match File::create(file_name) { Ok(value) => value, Err(error) => return Err(error), }; f.write_all(content.as_bytes()) }
It's up to you, but now you know what options you have!
Now let's check the last line:
f.write_all(content.as_bytes())
Nothing fancy here; we write all our data into the file. We just need to convert (it's not really a conversion in this case, more like getting internal data) our &str into a slice of u8 (so a &[u8]).
Now that we have a function to write a file, it'd be nice to be able to read from a file as well:
use std::fs::File; use std::io::{self, Read}; fn read_from_file(file_name: &str) -> io::Result<String> { let mut f = File::open(file_name)?; let mut content = String::new(); f.read_to_string(&mut content)?; Ok(content) }
Now let's go over what this function does quickly:
fn read_from_file(file_name: &str) -> io::Result<String> {
This time, it only takes a filename as an argument and returns a String if the reading was successful:
let mut f = File::open(file_name)?; let mut content = String::new(); f.read_to_string(&mut content)?;
Just like before, we open the file. Then we create a mutable String where the file content will be stored and finally we read all the file content at once with the read_to_string method (the String is reallocated as many times as needed). This method will fail if the string isn't proper UTF-8.
And to finish, if everything went fine, we return our content:
Ok(content)
So now, let's see how we can use this in our future tetris.