If I have a file like this:
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
Err("May
June")?
}
I get this result:
Error: "May\nJune"
Is it possible to get the unquoted string on output? Like this:
Error: May
June
I tried this:
Err("May
June").map_err(|e|
format!("{:?}", e)
)?
but it just made it worse:
Error: "\"May\\nJune\""
You have to print the error yourself instead of relying on the default fallback implementation.
main() -> Result<…> prints Debug version of the error (where strings are escaped). It's intended as a quick'n'dirty solution for examples or playpens, and not for real programs where anyone would care about presentation of the output.
Use:
fn main() {
if let Err(e) = run() {
eprintln!("{}", e);
std::process::exit(1);
}
}
fn run() -> Result<(), Box<dyn Error>> {
// etc
}
It will print the error using Display formatting.
There's nothing special about main()'s built-in error handling, so you're not losing anything by printing the error yourself.
There's also an alternative solution of implementing a custom Debug implementation on errors to make the Debug implementation print a non-debug one, but IMHO that's a hack, and needs more code than just doing the straightforward print. If you want that hack, have a look at the anyhow crate.
It might be overkill to pull in an extra dependency for this, but you can use the terminator crate, which offers a new error type intended to be returned from main that delegates to the Display implementation when printed with Debug. Then your example would look like this...
use terminator::Terminator;
fn main() -> Result<(), Terminator> {
Err("May
June")?
}
...and the output would be this:
Error: May
June
Related
I have a function foo that can't be modified and contains println! and eprintln! code in it.
fn foo() {
println!("hello");
}
After I call the function, I have to test what it printed so I want to capture the stdout/stderr into a variable.
I strongly recommend against doing this, but if you are using nightly and don't mind using a feature that seems unlikely to ever be stabilized, you can directly capture stdout and stderr using hidden functionality of the standard library:
#![feature(internal_output_capture)]
use std::sync::Arc;
fn foo() {
println!("hello");
eprintln!("world");
}
fn main() {
std::io::set_output_capture(Some(Default::default()));
foo();
let captured = std::io::set_output_capture(None);
let captured = captured.unwrap();
let captured = Arc::try_unwrap(captured).unwrap();
let captured = captured.into_inner().unwrap();
let captured = String::from_utf8(captured).unwrap();
assert_eq!(captured, "hello\nworld\n");
}
It's very rare that a function "cannot be changed", so I'd encourage you to do so and use dependency injection instead. For example, if you are able to edit foo but do not want to change its signature, move all the code to a new function with generics which you can test directly:
use std::io::{self, Write};
fn foo() {
foo_inner(io::stdout(), io::stderr()).unwrap()
}
fn foo_inner(mut out: impl Write, mut err: impl Write) -> io::Result<()> {
writeln!(out, "hello")?;
writeln!(err, "world")?;
Ok(())
}
See also:
How can I test stdin and stdout?
How to take ownership of T from Arc<Mutex<T>>?
How do I convert a Vector of bytes (u8) to a string?
Not sure if this would work on windows, but should work on unix like systems. You should replace the file descriptor to something you can read later. I don't think it is really easy.
I would suggest to use stdio_override which already does that for you using files. You can redirect it, then execute the function and the read the file content.
From the example:
use stdio_override::StdoutOverride;
use std::fs;
let file_name = "./test.txt";
let guard = StdoutOverride::override_file(file_name)?;
println!("Isan to Stdout!");
let contents = fs::read_to_string(file_name)?;
assert_eq!("Isan to Stdout!\n", contents);
drop(guard);
println!("Outside!");
The library also support anything that implements AsRawFd, through the override_raw call. Confirming that it will probably just work on unix.
Otherwise, you can check on the implementation on how it is done internally, and maybe you could bypass a writer instead of a file somehow.
Shadow println!:
use std::{fs::File, io::Write, mem::MaybeUninit, sync::Mutex};
static mut FILE: MaybeUninit<Mutex<File>> = MaybeUninit::uninit();
macro_rules! println {
($($tt:tt)*) => {{
unsafe { writeln!(&mut FILE.assume_init_mut().lock().unwrap(), $($tt)*).unwrap(); }
}}
}
fn foo() {
println!("hello");
}
fn main() {
unsafe {
FILE.write(Mutex::new(File::create("out").unwrap()));
}
foo();
}
given a std::time::SystemTime in the past, I'd like to manipulate elapsed() in a method via:
fn render(&self) -> Result<(), String> {
...
let elapsed = self.start.elapsed()?.as_secs();
...
}
however, ? operator wants to convert std::time::SystemTimeError to String, and From for std::time::SystemTimeError doesn't provide such a conversion. Unfortunately, it doesn't seem like you can:
impl From<std::time::SystemTimeError> for std::time::SystemTimeError {
fn from(e: std::time::SystemTimeError) -> Self { ... }
}
I really don't want to have to add match to handle this, nor do I simply want to unwrap() without error checking. I could define a fn that wraps the match and returns a Result<std::time::Duration, String>, but that seems misguided. What am I missing?
Update:
After much futzing around with snafu; yes, I really like it. Adding the SystemTimeError was a breeze. However, it took me a while to sort out how to deal with the errors returned from the other crated (where they are Result<(), String>. I finally found that I just needed to add a GenericError to my Error enum and then implement std::convert::From for Error to create the GenericError.
Most people (myself included) will advise against using String with Result, as you can lose Error information that way. However I don't think that's the whole story. For your example, you can use the venerable map_err:
let elapsed = self.start.elapsed().map_err(|e|
e.to_string()
)?;
as detailed by Andrew Gallant. If you want to include full Error object,
you can do something like this:
let elapsed = self.start.elapsed().map_err(|e|
format!("{:?}", e)
)?;
Or, depending on the situation you might be able to avoid both Results and
panics:
let elapsed = self.start.elapsed().unwrap_or_default();
I find the message produced by expect() to be very unfriendly for users. Consider the following short example...
use std::env;
fn main() {
let imagefn = env::args().skip(1).next().expect("Filename not provided.");
println!("{}", imagefn);
}
That errors with:
thread 'main' panicked at 'Filename not provided.', libcore/option.rs:960:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.
I find expect() very useful for writing quick code, but wish I could output something more like this:
Filename not provided.
With all the other information hidden unless I actually provide the environment variable, which I, as a developer, should know about. I guess my questions are:
Is there a way I can override expect() to do this?
Why does expect() output its unfriendly message even in release builds?
You can use set_hook to change the panic message. Example:
use std::panic::set_hook;
fn main() {
set_hook(Box::new(|info| {
if let Some(s) = info.payload().downcast_ref::<String>() {
println!("{}", s);
}
}));
// Displays: "My error message":
Option::None::<i32>.expect("My error message");
}
You can also use message() that is simpler, but unstable (for now):
#![feature(panic_info_message)]
use std::panic::set_hook;
fn main() {
set_hook(Box::new(|info| {
println!("{:?}", info.message().unwrap());
}));
Option::None::<i32>.expect("My error message");
}
Note that you can create your own extension method that panics with a custom type. In the panic hook, if you can downcast to your custom type, you are certain of the origin of the panic.
expect() is just a convenient conditional call to panic!():
pub fn expect(self, msg: &str) -> T {
match self {
Some(val) => val,
None => expect_failed(msg) // expect_failed calls panic!()
}
}
Ideally you should probably handle this with the ? operator inside a function returning an Option or Result in order to be able to handle this sort of issue in a more graceful manner.
If you would just like to return a more friendly-looking message and quit, you could implement your own function printing the message and terminating with process::exit.
Result.expect()'s console output wasn't what I needed, so I extended Result with my own version:
trait ResultExt<T> {
fn or_exit(self, message: &str) -> T;
}
impl<T> ResultExt<T> for ::std::result::Result<T, Error> {
fn or_exit(self, message: &str) -> T {
if self.is_err() {
io::stderr().write(format!("FATAL: {} ({})\n", message, self.err().unwrap()).as_bytes()).unwrap();
process::exit(1);
}
return self.unwrap();
}
}
As I understand, Rust doesn't support varargs yet, so I have to use it like that, correct?
something().or_exit(&format!("Ah-ha! An error! {}", "blah"));
That's too verbose compared to either Java, Kotlin or C. What is the preferred way to solve this?
I don't think the API you suggested is particularly unergonomic. If maximum performance matters, it might make sense to put the error generation in a closure or provide an API for that too, so the String is only allocated when there is actually an error, which might be especially relevant when something is particularly expensive to format. (Like all the _else methods for std::result::Result.)
However, you might be able to make it more ergonomic by defining a macro which takes a result, a &str and format parameters. This could look like this for example: (This is based on #E_net4's comment)
macro_rules! or_exit {
($res:expr, $fmt:expr, $($arg:tt)+) => {
$res.unwrap_or_else(|e| {
let message = format!($fmt, $($arg)+);
eprintln!("FATAL: {} ({})\n", message, e);
process::exit(1)
})
};
}
fn main() {
let x: Result<i32, &'static str> = Err("dumb user, please replace");
let _ = or_exit!(x, "Ah-ha! An error! {}", "blahh");
}
Rust Playground
Note this might not yield the best error messages if users supply invalid arguments, I did not want to change your code too much, but if you decide to actually have the macro only be sugar and nothing else you should probably extend your API to take a closure instead of a string. You might want also to reconsider the naming of the macro.
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()
}