patternrustMajor
Why do try!() and ? not compile when used in a function that doesn't return Option or Result?
Viewed 0 times
compilereturnfunctionwhyanduseddoesnoptionresultwhen
Problem
Why does this code not compile?
This is the error I get
I also tried the question mark operator:
Which had a different error:
Previous versions of Rust had a similar error about
Should I avoid
But if I have to use that in a
use std::{fs, path::Path};
fn main() {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
println!("Is not a directory");
return;
}
for item in try!(fs::read_dir(dir)) {
let file = match item {
Err(e) => {
println!("Error: {}", e);
return;
}
Ok(f) => f,
};
println!("");
}
println!("Done");
}This is the error I get
error[E0308]: mismatched types
--> src/main.rs:11:17
|
11 | for item in try!(fs::read_dir(dir)) {
| ^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum std::result::Result
|
= note: expected type ()
found type std::result::Result
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
I also tried the question mark operator:
for item in fs::read_dir(dir)? {Which had a different error:
error[E0277]: the ? operator can only be used in a function that returns Result or Option (or another type that implements std::ops::Try)
--> src/main.rs:11:17
|
11 | for item in fs::read_dir(dir)? {
| ^^^^^^^^^^^^^^^^^^ cannot use the ? operator in a function that returns ()
|
= help: the trait std::ops::Try is not implemented for ()
= note: required by std::ops::Try::from_error
Previous versions of Rust had a similar error about
std::ops::CarrierShould I avoid
try!() and ?? What is the best way to handle errors? Mostly I do it like this:match error_prone {
Err(e) => {
println!("Error: {}", e);
return;
},
Ok(f) => f,
};But if I have to use that in a
for loop, it's a complete messfor i in match error_prone {
// match code
} {
// loop code
}Solution
try! is a macro that returns Errs 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 Results.As of Rust 1.26.0,
main is allowed to return a value that implements Termination. Before that, it doesn't return any value.As of Rust 1.26.0
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 {
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(())
}Before that
This is how you might transform your code to use
?:use std::{error::Error, fs, path::Path};
fn print_dir_contents() -> Result> {
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 {
// ...
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 {
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()),
}
}Code Snippets
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(())
}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()),
}
}path.file_name().unwrap()file_name.to_string_lossy()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!"));
}
// ...
}Context
Stack Overflow Q#30555477, score: 66
Revisions (0)
No revisions yet.