I'm writing a Rust program which will create a directory based on user input. I would like to know how to panic with my own text when error occurs, like Permission Error etc...
fn create_dir(path: &String) -> std::io::Result<()> {
std::fs::create_dir_all(path)?;
Ok(())
}
This will do nothing when error occcurs
For this case, the simplest way is to use unwrap_or_else():
fn create_dir(path: &str) {
std::fs::create_dir_all(path)
.unwrap_or_else(|e| panic!("Error creating dir: {}", e));
}
Note that I also changed the argument type, for the reasons described here.
However, it would be more idiomatic to accept a &Path or AsRef<Path>.
use std::fs;
use std::path::Path;
fn create_dir<P: AsRef<Path>>(path: P) {
fs::create_dir_all(path)
.unwrap_or_else(|e| panic!("Error creating dir: {}", e));
}
Related
What would be the proper way to store tiny bit of data to reuse?
I am thinking about creating options.json file. Any instruments for it?
Another options?
I was a bit bored, so here is the solution you were looking for. It attempts to save an options.json file in the same directory as the executable.
use serde::{Serialize, Deserialize};
use serde::de::DeserializeOwned;
use std::env::current_exe;
use std::io::{self, BufReader, BufWriter, Error, ErrorKind};
use std::fs::File;
pub fn save_data_for_next<D: Serialize>(data: &D) -> io::Result<()> {
let options_path = current_exe()?.parent().unwrap().join("options.json");
let writer = BufWriter::new(File::create(options_path)?);
serde_json::to_writer(writer, data).map_err(|e| Error::new(ErrorKind::Other, e))
}
pub fn load_previous_data<D: DeserializeOwned>() -> io::Result<Option<D>> {
let options_path = current_exe()?.parent().unwrap().join("options.json");
if !options_path.is_file() {
return Ok(None)
}
let reader = BufReader::new(File::open(options_path)?);
match serde_json::from_reader(reader) {
Ok(v) => Ok(Some(v)),
Err(e) => Err(Error::new(ErrorKind::Other, e))
}
}
Then all you need to do to use it is to derive Serialize and Deserialize on some type. Alternatively you could use serde_json::Value, then it would be able to safely save/load any arbitrary JSON values. However, you may need to manually delete the options.json file when you change the contents of Options since it may panic upon failing to parse the previous version.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Options {
window_position: (u32, u32),
}
pub fn main() {
let mut options = match load_previous_data::<Options>().unwrap() {
Some(v) => v,
// Options has not been created yet, so create some default config
None => Options {
window_size: (500, 500),
},
};
// Run program
// Save options before exiting
save_data_for_next(&options).unwrap();
}
I would like to have the details of an error be propagated upwards. I used error-chain previously, but that has not been maintained or kept compatible with the rest of the ecosystem as far as i can tell.
For example, in this example:
use std::str::FromStr;
use anyhow::Result;
fn fail() -> Result<u64> {
Ok(u64::from_str("Some String")?)
}
fn main() {
if let Err(e) = fail(){
println!("{:?}", e);
}
The error i am getting is:
invalid digit found in string
I would need the error message to have the key details, including at the point of failure, for example:
- main: invalid digit found in string
- fail: "Some String" is not a valid digit
What's the best way of doing this?
anyhow provides the context() and with_context() methods for that:
use anyhow::{Context, Result};
use std::str::FromStr;
fn fail() -> Result<u64> {
let s = "Some String";
Ok(u64::from_str(s).with_context(|| format!("\"{s}\" is not a valid digit"))?)
}
fn main() {
if let Err(e) = fail() {
println!("{:?}", e);
}
}
"Some String" is not a valid digit
Caused by:
invalid digit found in string
If you want custom formatting, you can use the Error::chain() method:
if let Err(e) = fail() {
for err in e.chain() {
println!("{err}");
}
}
"Some String" is not a valid digit
invalid digit found in string
And if you want additional details (e.g. where the error happened), you can use a custom error type and downcast it (for error source you can also capture a backtrace).
This is a tricky thing to accomplish and I'm not sure that there is a simple and non-invasive way to capture all of the details of any possible error without knowledge of the particular function being invoked. For example, we may want to display some arguments to the function call that failed, but evaluating other arguments might be problematic -- they may not even be able to be turned into strings.
Maybe the argument is another function call, too, so should we capture its arguments or only its return value?
I whipped up this example quickly to show that we can at least fairly trivially capture the exact source expression. It provides a detail_error! macro that takes an expression that produces Result<T, E> and emits an expression that procudes Result<T, DetailError<E>>. The DetailError wraps the original error value and additionally contains a reference to a string of the original source code fed to the macro.
use std::error::Error;
use std::str::FromStr;
#[derive(Debug)]
struct DetailError<T: Error> {
expr: &'static str,
cause: T,
}
impl<T: Error> DetailError<T> {
pub fn new(expr: &'static str, cause: T) -> DetailError<T> {
DetailError { expr, cause }
}
// Some getters we don't use in this example, but should be present to have
// a complete API.
#[allow(dead_code)]
pub fn cause(&self) -> &T {
&self.cause
}
#[allow(dead_code)]
pub fn expr(&self) -> &'static str {
self.expr
}
}
impl<T: Error> Error for DetailError<T> { }
impl<T: Error> std::fmt::Display for DetailError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "While evaluating ({}): ", self.expr)?;
std::fmt::Display::fmt(&self.cause, f)
}
}
macro_rules! detail_error {
($e:expr) => {
($e).map_err(|err| DetailError::new(stringify!($e), err))
}
}
fn main() {
match detail_error!(u64::from_str("Some String")) {
Ok(_) => {},
Err(e) => { println!("{}", e); }
};
}
This produces the runtime output:
While evaluating (u64::from_str("Some String")): invalid digit found in string
Note that this only shows the string because it's a literal in the source. If you pass a variable/parameter instead, you will see that identifier in the error message instead of the string.
When you run your app with the environment variable RUST_BACKTRACE set to 1 or full, you'll get more error details, without the need to recompile your program. That, however, doesn't mean you're going to get an extra message like "Some String" is not a valid digit, as the parsing function simply doesn't generate such.
This question already has an answer here:
Why am I getting "unused Result which must be used ... Result may be an Err variant, which should be handled" even though I am handling it?
(1 answer)
Closed 2 years ago.
I'm doing rust cli tutorials from https://rust-cli.github.io/book/tutorial/testing.html and got stuck with a compiler warning:
unused `std::result::Result` that must be used
--> src/main.rs:15:13
|
15 | writeln!(writer, "{}", line);
Here is the whole code:
use exitfailure::ExitFailure;
use failure::ResultExt;
use structopt::StructOpt;
#[derive(StructOpt)]
struct Cli {
pattern: String,
#[structopt(parse(from_os_str))]
path: std::path::PathBuf,
}
fn find_matches(content: &str, pattern: &str, mut writer: impl std::io::Write) {
for line in content.lines() {
if line.contains(pattern) {
writeln!(writer, "{}", line);
}
}
}
#[test]
fn find_a_match() {
let mut result = Vec::new();
find_matches("lorem ipsum\ndolor sit amet", "lorem", &mut result);
assert_eq!(result, b"lorem ipsum\n");
}
fn main() -> Result<(), ExitFailure> {
let args = Cli::from_args();
let content = std::fs::read_to_string(&args.path)
.with_context(|_| format!("could not read file `{}`", &args.path.display()))?;
find_matches(&content, &args.pattern, &mut std::io::stdout());
Ok(())
}
Why do I get this warning?
writer that you pass to writeln! can be arbitrary std::io::Write object, in particular a file writer, a network writer or whatever. Those can fail when you write to them and so writeln!(...) returns a Result<T,E>, which indicates whether write operation completed successfully or failed.
If you don't use this Result, then you can potentially miss when an error is occurred, so the logic of your program might fail then. (note that Rust doesn't have exceptions, so there are in general two ways of telling a problem: in a return type like Option, Result, bool, or whatever; or via panic!, assert! macros that will crash your program with no chance to recover if something went wrong).
So ideally you shouldn't ignore this Result in any way. You can do like this for now
for line in content.lines() {
if line.contains(pattern) {
if let Err(e) = writeln!(writer, "{}", line) {
println!("Writing error: {}", e.to_string());
}
}
}
If you wish, later on you can make a more adequate reaction to the error (like retrying writing)
In Bash this would be ${0##*/}.
use std::env;
use std::path::Path;
fn prog() -> String {
let prog = env::args().next().unwrap();
String::from(Path::new(&prog).file_name().unwrap().to_str().unwrap())
}
fn main() {
println!("{}", prog());
}
Is there a better way? (I particularly dislike those numerous unwrap()s.)
If you don't care about why you can't get the program name, you can handle all the potential errors with a judicious mix of map and and_then. Additionally, return an Option to indicate possible failure:
use std::env;
use std::path::Path;
use std::ffi::OsStr;
fn prog() -> Option<String> {
env::args().next()
.as_ref()
.map(Path::new)
.and_then(Path::file_name)
.and_then(OsStr::to_str)
.map(String::from)
}
fn main() {
println!("{:?}", prog());
}
If you wanted to follow delnan's awesome suggestion to use std::env::current_exe (which I just learned about!), replace env::args().next() with env::current_exe().ok().
If you do want to know why you can't get the program name (and knowing why is usually the first step to fixing a problem), then check out ker's answer.
You can also get rid of the unwraps and still report all error causes properly (instead of munching them into a "something failed" None). You aren't even required to specify the full paths to the conversion methods:
fn prog() -> Result<String, ProgError> {
let path = try!(env::current_exe());
let name = try!(path.file_name().ok_or(ProgError::NoFile));
let s_name = try!(name.to_str().ok_or(ProgError::NotUtf8));
Ok(s_name.to_owned())
}
Together with the future questionmark operator this can also be written as a single dot call chain:
fn prog() -> Result<String, ProgError> {
Ok(env::current_exe()?
.file_name().ok_or(ProgError::NoFile)?
.to_str().ok_or(ProgError::NotUtf8)?
.to_owned())
}
Of course this has the prerequisite of the ProgError type:
use std::io::Error;
#[derive(Debug)]
enum ProgError {
NoFile,
NotUtf8,
Io(Error),
}
impl From<Error> for ProgError {
fn from(err: Error) -> ProgError {
ProgError::Io(err)
}
}
try it out on the Playground
Just one more Option version :)
fn prog() -> Option<String> {
std::env::current_exe()
.ok()?
.file_name()?
.to_str()?
.to_owned()
.into()
}
I am an experienced developer currently trying to teach myself Rust and am writing a simple program to read lines from a file. I have read the Rust std:io, std:result and other documentation forward and backwards and this code is largely taken straight from the docs. I can't understand why the following program does not compile.
use std::io;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
fn main() {
File::open("./data/test")
.map_err(|err| err.to_string())
.and_then( |mut dataFile| {
let mut reader = BufReader::new(dataFile);
for line in reader.lines() {
println!("{}",line.unwrap());
};
});
}
The compile error I am receiving when I run cargo build is
src/main.rs:10:35: 16:10 error: mismatched types:
expected `core::result::Result<_, collections::string::String>`,
found `()`
(expected enum `core::result::Result`,
found ()) [E0308]
src/main.rs:10 .and_then( |mut dataFile| {
src/main.rs:11 let mut reader = BufReader::new(dataFile);
src/main.rs:12 for line in reader.lines() {
src/main.rs:13 println!("{}",line.unwrap());
src/main.rs:14
src/main.rs:15 };
...
src/main.rs:10:35: 16:10 help: run `rustc --explain E0308` to see a detailed explanation
error: aborting due to previous error
I am running Rust 1.4.0.
You shouldn't be using and_then, but map.
Correct code:
File::open("./data/test")
.map_err(|err| err.to_string())
.map(|mut dataFile| {
let mut reader = BufReader::new(dataFile);
for line in reader.lines() {
println!("{}",line.unwrap());
};
});
You can see it in both functions signatures:
fn and_then<U, F: FnOnce(T) -> Result<U, E>>(self, op: F) -> Result<U, E>
fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E>
The key difference here is that the operation op must return something wrapped in a Result in and_then's case, whereas it doesn't have to be wrapped in map's case. Your closure doesn't return anything, so in Rust's view it actually returns (), and () cannot match Result<U, E>. However () can match U, which is why the second signature works.
As mdup explains, map and and_then are used to transform a Result with a closure. map is used when you want to change the inner type, and_then is used when you want to chain a second thing that results in another Result without nesting them.
However, neither of these is idiomatic for your case as you are not transforming the value. Instead, you want a match or if let statement. These are more appropriate for side effects.
Additionally, Rust uses snake_case identifiers, and you don't need to have any of the variables be marked as mutable. These are all compiler warnings you would see once it compiles.
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
fn main() {
let f = File::open("./data/test")
.map_err(|err| err.to_string());
if let Ok(data_file) = f {
let reader = BufReader::new(data_file);
for line in reader.lines() {
println!("{}", line.unwrap());
};
}
}