In this lesson we’re going to learn how to create a file.
let file = File::create(filename).unwrap();
The Code
We’re going to end up with this code.
use std::{env, fs::File};
fn main() {
let args: Vec<String> = env::args().collect();
let layout = &args[1];
let tags = &args[2];
let heading = &args[3];
let filename = &args[4];
let new_file_contents = format!(
"---
layout: {layout}
tags: {tags}
status: draft
---
# {heading}
"
);
let file = File::create(filename).unwrap();
}
Creating a new file
Earlier in this workshop we uses the args function from the std::env module.
In a similar fashion, to create a file we’ll use the create function that is associated with the File struct in the std::fs module.
We’ll bring the File struct into scope with use right at the top of main.rs. We can merge the use items to bring std::env and std::fs::File into scope at the same time.
use std::{env, fs::File};
Then at the bottom of the file, we’ll use File::create to create a new file.
File::create(filename);
We can use cargo run with a set of arguments and see our program create a file now. In this case, the file is doing-shader-stuff-in-bevy.md
❯ cargo run post rust,bevy "Doing shader stuff in Bevy" doing-shader-stuff-in-bevy.md
Compiling first v0.1.0 (/rust-adventure/first-cli)
warning: unused variable: `new_file_contents`
--> src/main.rs:11:9
|
11 | let new_file_contents = format!(
| ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_new_file_contents`
|
= note: `#[warn(unused_variables)]` on by default
warning: unused `Result` that must be used
--> src/main.rs:23:5
|
23 | File::create(filename);
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled
warning: `first` (bin "first") generated 2 warnings
Finished dev [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/first post rust,bevy 'Doing shader stuff in Bevy' doing-shader-stuff-in-bevy.md`
The created file will be empty because we haven’t written the template into it yet.
We get two warnings when running the program, an unused variable warning for new_file_contents, which we’ll ignore, and an unused Result that must be used.
Let’s dig into the create function to find out why the second warning is happening.
The create function
The docs for the create function tell us what arguments the function accepts and what the return value is.
I’m going to walk you through the documentation for this function and how to read the type signature. Don’t worry about understanding all of what I’m about to explain, you can always come back to it.
The tldr; of this part is that create takes a string and returns a Result.
pub fn create<P: AsRef<Path>>(path: P) -> Result<File>
The create function is a public function that accepts an argument that we’ve named path.
path is of type P, which is a generic type argument. This means the type is defined by the constraints we place on it. In this case we are using the P: syntax to say that P has to implement the AsRef trait.
The AsRef trait is a trait that can be implemented to convert a shared reference of one type, into a shared reference of another type. Specifically, this function accepts any argument that can become a Path.
If we look at the Path docs, we can find an AsRef<Path> implementation for str and String, Rust’s string types.
So create accepts any value as an argument as long as the type of that value implements AsRef<Path>.
The Result enum
So now we know why and how the create function can accept a string as an argument. The return type is a Result<File>. If we click on the documentation link, we find out that that the Result we’re looking at is in the std::io module, at std::io::Result and is a type alias for the Result enum from std::result::Result.
This is fine, we can name types the same name as long as they have different module paths.
In this case, the return type will behave like the type std::result::Result<File, std::io::Error>.
This type tells us what a successful value will be as well as an unsuccessful value.
A Result is an ordinary enum. When you have a variable that is a Result it means that you either have a value wrapped in the Ok variant or the Err variant.
In our case we’ll either have an Ok with a File in it, or an Err with a std::io::Error in it.
Handling Results
There are a bunch of ways to handle a Result but right now, we mostly don’t care about the error and we want the File inside of the Result, if it exists.
The most straightforward way to do this is to use unwrap.
If the value returned from create is an Err, then unwrap will crash our program. This is fine for our small CLI application since if we can’t create a file then our file scaffolding CLI can’t actually proceed successfully.
If the value returned from create is an Ok, then unwrap will “unwrap” the result container and give us the value.
let file = File::create(filename).unwrap();
This means our file variable will either have the File that just got created, or our program will have crashed.
When we run the program now, we’ll see an empty file created at the location we specified.
cargo run post rust,bevy "Doing shader stuff in Bevy" doing-shader-stuff-in-bevy.md