Why does this code not compile?
use std::{fs, path::Path};
fn main() {
let dir = Path::new(\"../FileSystem\");
if !dir.is_dir() {
println!(
Veedrac's answer helped me too, although the OP's question is slightly different. While reading the Rust documentation, I saw this snippet:
use std::fs::File;
use std::io::prelude::*;
let mut file = File::open("foo.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
assert_eq!(contents, "Hello, world!");
Though in the Rust Book they point out the centrality of the main function, if you run this inside it you'll get a similar error. If you wrap the code inside a function handling the errors the aforementioned snippet works:
use std::error::Error;
use std::io::prelude::*;
use std::fs::File;
fn print_file_content() -> Result<String, Box<Error>> {
let mut f = File::open("foo.txt")?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
println!("The content: {:?}", contents);
Ok("Done".into())
}
fn main() {
match print_file_content() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
P.S. I'm learning Rust so these snippets are not intended as good Rust coding :)
As of Rust 1.26, Rust supports a return value from main(), and thus supports the use of the error-check operator ?
(or equivalently the try!()
macro) in main()
when main()
is defined to return a Result
:
extern crate failure;
use failure::Error;
use std::fs::File;
type Result<T> = std::result::Result<T, Error>;
fn main() -> Result<()> {
let mut _file = File::open("foo.txt")?; // does not exist; returns error
println!("the file is open!");
Ok(())
}
The above compiles and returns a file not found error (assuming foo.txt
does not exist in the local path).
Rust playground example
try!
is a macro that returns Err
s automatically; ?
is syntax that does mostly the same thing, but it works with any type that implements the Try trait.
As of Rust 1.22.0, Option
implements Try
, so it can be used with ?
. Before that, ?
could only be used in functions that return a Result
. try!
continues to only work with Result
s.
As of Rust 1.26.0, main
is allowed to return a value that implements Termination
. Before that, it doesn't return any value.
Your original code works if you mark main
as returning a Result
and then return Ok(())
in all the "success" cases:
use std::{fs, io, path::Path};
fn main() -> Result<(), io::Error> {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
println!("Is not a directory");
return Ok(());
}
for item in fs::read_dir(dir)? {
let file = match item {
Err(e) => {
println!("Error: {}", e);
return Ok(());
}
Ok(f) => f,
};
println!("");
}
println!("Done");
Ok(())
}
This is how you might transform your code to use ?
:
use std::{error::Error, fs, path::Path};
fn print_dir_contents() -> Result<String, Box<Error>> {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
return Err(Box::from("Is not a directory!"));
}
for entry in fs::read_dir(dir)? {
let path = entry?.path();
let file_name = path.file_name().unwrap();
println!("{}", file_name.to_string_lossy());
}
Ok("Done".into())
}
fn main() {
match print_dir_contents() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
There's a lot of error handling here that you might not expect - other languages don't tend to require it! But they exist in other languages - Rust just makes you know it. Here are the errors:
entry?
IO errors can happen during iteration.
path.file_name().unwrap()
Not all paths have file names. We can unwrap
this because read_dir
won't give us a path without a file name.
file_name.to_string_lossy()
You can also to_str
and throw an error, but it's nicer to do this. This error exists because not all file names are valid Unicode.
try!
and ?
throw errors into the return value, converting them to Box::Error
. It's actually more reasonable to return an amalgamated error of all the things that can go wrong. Luckily io::Error
is just the right type:
use std::io;
// ...
fn print_dir_contents() -> Result<String, io::Error> {
// ...
if !dir.is_dir() {
return Err(io::Error::new(io::ErrorKind::Other, "Is not a directory!"));
}
// ...
}
Frankly, though, this check is already in fs::read_dir
, so you can actually just remove the if !dis.is_dir
altogether:
use std::{fs, io, path::Path};
fn print_dir_contents() -> Result<String, io::Error> {
let dir = Path::new("../FileSystem");
for entry in fs::read_dir(dir)? {
let path = entry?.path();
let file_name = path.file_name().unwrap();
println!("{}", file_name.to_string_lossy());
}
Ok("Done".into())
}
fn main() {
match print_dir_contents() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
The ques_in_main RFC got merged recently. Once it's completed, the syntax in the question will indeed compile just fine and work as intended, provided the try!()
calls are replaced with the ?
operator.