How call function on Err arm - rust

I wrote this code and it works. But I have a custom class for outputting logs to stdout. I want to call my logging function if I get an Error instead of panic! macro.
fn create_logfile() -> File {
let timestamp = Log::format_date(chrono::Local::now());
let filename = format!("{}.log", timestamp);
let logfile = match File::create(filename) {
Ok(file) => file,
Err(error) => {
panic!("There was a problem creating the file: {:?}", error)
}
};
logfile
}
For example I want get something like that:
let logfile = match File::create(filename) {
Ok(file) => file,
Err(e) => {
Log::error("Log file creation failed, reason: {}", e);
process::exit(1)
}
};
But compiler says:
[E0308] `match` arms have incompatible types.
[Note] expected struct `File`, found `()
How can I solve this problem?
If I put the error data to stderr will it help?

Your revised example with std::process::exit() works for me: (link)
use chrono; // 0.4.23
use std::fs::File;
use log; // 0.4.17
struct Log { }
impl Log {
fn format_date(date: chrono::DateTime<chrono::offset::Local>) -> i64 {
return 0;
}
}
fn old_create_logfile() -> File {
let timestamp = Log::format_date(chrono::Local::now());
let filename = format!("{}.log", timestamp);
let logfile = match File::create(filename) {
Ok(file) => file,
Err(error) => {
panic!("There was a problem creating the file: {:?}", error)
}
};
logfile
}
fn new_create_logfile() -> File {
let timestamp = Log::format_date(chrono::Local::now());
let filename = format!("{}.log", timestamp);
let logfile = match File::create(filename) {
Ok(file) => file,
Err(e) => {
// Instead of using `Log::error`, we'll use `log::error!` for show.
log::error!("Log file creation failed, reason: {}", e);
// This compiles.
std::process::exit(1)
}
};
logfile
}
fn main() {
new_create_logfile();
}
Normally, you need to make sure the return types of all match arms have the same type -- here, you are returning std::fs::File under the Ok branch, so the Err branch can "escape" the requirement by returning ! (pronounced "never") (link).
Since computation never returns from the std::process::exit() and its return type is marked as !, it's passes the type-checking stage.

Related

Do nothing and throw an error if condition is not met

At the moment I'm running a panic! macro every time I the extension is not met. But this is not how it should be, I want it to just throw an error and/or skip and do nothing. Is there a way to throw an error instead and/or skip it.
impl FileMetaData {
fn new(path: &str) -> FileMetaData {
FileMetaData {
name: FileMetaData::get_file_name(&path).to_string(),
directory: FileMetaData::is_directory(&path),
path: path.to_string(),
}
}
fn get_file_name(path: &str) -> &str {
let file_path = Path::new(path);
let file_name_os_str = file_path.file_stem().unwrap();
if !FileMetaData::is_directory(path) {
if file_path.extension().unwrap().to_str().unwrap() != "heic" {
// TO DO: Change to Err or skip if file not supported
// TO DO: Possible change the whole flow of converting the image
panic!("File format not supported");
}
}
return file_name_os_str.to_str().unwrap()
}
fn is_directory(path: &str) -> bool {
Path::new(&path).is_dir()
}
}
I have 2 use case on the FileMetaData struct since I'm accepting 2 kind of path, a file and a directory.
For file, it's easier since I can just throw an error and it would exit.
But for directory, I need it to not exit if there is a unsupported format detected.
# file
let file = FileMetaData::new(&file_path);
# directory
let entries = fs::read_dir(&path)?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, io::Error>>()?;
for entry in entries {
let file_path = file.to_str().unwrap();
# this should just print an error and continue for the
# other entries in list
let file_metadata = FileMetaData::new(&file_path);
}
You can use continue to skip to the next iteration of a loop.
Method 1: Using let-else
for entry in entries {
let Ok(data) = might_fail() else {
eprintln!("An error occurred!");
continue;
};
}
Downsides: Err is not exposed to the error handling block.
Method 2: Using match
for entry in entries {
let data = match might_fail() {
Ok(data) => data,
Err(e) => {
eprintln!("An error occurred: {e}");
continue;
}
};
}

Assigning returned value in a match arm to a new variable

I am working on my first Rust project, a CLI application to find large files on a local filesystem. Rust has excellent documentation regarding match statements but I do not see how I can assign a value returned by the function passed to the match statement in a new variable:
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let mut results_map: Option<Results>;
match search(&config.root_path) {
Err(e) => eprintln!("Error when calling run(): {:?}", e),
Ok(results) => results_map = results), // how to properly assign value returned by function here?
}
Ok(())
}
pub struct Results {
result_map: HashMap<String, u64>,
}
pub fn search(path: &str) -> Result<Results, io::Error> {
let root_path = Path::new(path);
let mut results = Results { result_map: HashMap::<String, u64>::new()};
match visit_dirs(root_path, &mut results) {
Err(e) => eprintln!("Error calling visit_dirs() from search(): {:?}", e),
_ => (),
}
Ok(results)
}
The functions you're calling in the match statements, search and visit_dirs return Result types. You have to use one of the arms of the match statement to cover the Ok (happy) case, next to where you're already covering the Err (error) case:
match search(&config.root_path) {
Ok(results) => return results,
Err(e) => eprintln!("Error when calling run(): {:?}", e),
}
Or you can assign the result of the match expression to a new variable:
let new_variable = match search(&config.root_path) {
Ok(results) => results, // no return here
Err(e) => {
eprintln!("Error when calling run(): {:?}", e)
// Either choose to panic (not recommended)
// panic!("Bad times lie ahead")
// Or propagate the error from your own Result
return Err(e);
},
}
You may want to look into the ? operator to simplify this in the common case.

How can I rename a file given a Path once the path has been moved?

I am trying to rename a file named settings.ron -> setting.invalid.ron, given the path to settings.ron which might change depending on the environment.
I am running into trouble with moving the value of path when it was already borrowed in the File reading operation.
impl Settings {
pub fn load() -> Self {
let path = Settings::get_settings_path();
if let Ok(file) = fs::File::open(path) {
match ron::de::from_reader(file) {
Ok(x) => x,
Err(e) => {
log::warn!("Failed to parse setting file! Fallback to default. {}", e);
path.with_file_name("settings.invalid.ron");
}
}
}
let default_settings = Self::default();
default_settings.save_to_file_warn();
default_settings
}
}
I got it working, but it seems odd to have to create an extra variable:
impl Settings {
pub fn load() -> Self {
let path = Settings::get_settings_path();
let mut path_buf = path.to_owned();
if let Ok(file) = fs::File::open(path) {
match ron::de::from_reader(file) {
Ok(x) => x,
Err(e) => {
log::warn!("Failed to parse setting file! Fallback to default. {}", e);
path_buf.set_file_name("settings.invalid.ron");
}
}
}
let default_settings = Self::default();
default_settings.save_to_file_warn();
default_settings
}
}
As Sven Marnach said, the trick was to pass a reference to File::open():
impl Settings {
pub fn load() -> Self {
let mut path = Settings::get_settings_path();
if let Ok(file) = fs::File::open(&path) {
match ron::de::from_reader(file) {
Ok(x) => x,
Err(e) => {
log::warn!("Failed to parse setting file! Fallback to default. {}", e);
// Rename the corrupted settings file
let mut new_path = path.to_owned();
new_path.pop();
new_path.push("settings.invalid.ron");
if let Err(err) = std::fs::rename(path, new_path) {
log::warn!("Failed to rename settings file. {}", err);
}
}
}
}
let default_settings = Self::default();
default_settings.save_to_file_warn();
default_settings
}
}

How to return an error from FuturesUnordered?

I have a set of futures to be run in parallel and if one fails I would like to get the error to return to the caller.
Here is what I have been testing so far:
use futures::prelude::*;
use futures::stream::futures_unordered::FuturesUnordered;
use futures::{future, Future};
fn main() {
let tasks: FuturesUnordered<_> = (1..10).map(|_| async_func(false)).collect();
let mut runtime = tokio::runtime::Runtime::new().expect("Unable to start runtime");
let res = runtime.block_on(tasks.into_future());
if let Err(_) = res {
println!("err");
}
}
fn async_func(success: bool) -> impl Future<Item = (), Error = String> {
if success {
future::ok(())
} else {
future::err("Error".to_string())
}
}
How can I get the error from any failed futures? Even better would be to stop running any pending futures if a single future fails.
Your code is already returning and handling the error. If you attempted to use the error, the compiler will quickly direct you to the solution:
if let Err(e) = res {
println!("err: {}", e);
}
error[E0277]: `(std::string::String, futures::stream::futures_unordered::FuturesUnordered<impl futures::future::Future>)` doesn't implement `std::fmt::Display`
--> src/main.rs:12:29
|
12 | println!("err: {}", e);
| ^ `(std::string::String, futures::stream::futures_unordered::FuturesUnordered<impl futures::future::Future>)` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `(std::string::String, futures::stream::futures_unordered::FuturesUnordered<impl futures::future::Future>)`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required by `std::fmt::Display::fmt`
The Err value is a tuple of your error and the original stream to continue pulling after you have dealt with the error. This is what Stream::into_future / StreamFuture does.
Access the first value in the tuple to get to the error:
if let Err((e, _)) = res {
println!("err: {}", e);
}
If you want to see all of the values, you could keep polling the stream over and over (but don't do this because it's probably inefficient):
let mut f = tasks.into_future();
loop {
match runtime.block_on(f) {
Ok((None, _)) => {
println!("Stream complete");
break;
}
Ok((Some(v), next)) => {
println!("Success: {:?}", v);
f = next.into_future();
}
Err((e, next)) => {
println!("Error: {:?}", e);
f = next.into_future();
}
}
}

rust create a String from file.read_to_end()

I am trying to read a file and return it as a UTF-8 std:string:String it seems like content is a Result<collections::string::String, collections::vec::Vec<u8>> if i understand an error message i got from trying String::from_utf8(content).
fn get_index_body () -> String {
let path = Path::new("../html/ws1.html");
let display = path.display();
let mut file = match File::open(&path) {
Ok(f) => f,
Err(err) => panic!("file error: {}", err)
};
let content = file.read_to_end();
println!("{} {}", display, content);
return String::new(); // how to turn into String (which is utf-8)
}
Check the functions provided by the io::Reader trait: http://doc.rust-lang.org/std/io/trait.Reader.html
read_to_end() returns IoResult<Vec<u8>>, read_to_string() returns IoResult<String>.
IoResult<String> is just a handy way to write Result<String, IoError>: http://doc.rust-lang.org/std/io/type.IoResult.html
You can extract Strings from a Result either using unwrap():
let content = file.read_to_end();
content.unwrap()
or by handling the error yourself:
let content = file.read_to_end();
match content {
Ok(s) => s,
Err(why) => panic!("{}", why)
}
See also: http://doc.rust-lang.org/std/result/enum.Result.html

Resources