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