How can I get the message string from std::io::Error? - rust

This works:
format!("{:?}", error))
// Os { code: 13, kind: PermissionDenied, message: "Permission denied" }
But I want only the message field, not the full debug print. How do I get it?
error.message // unknown field
error.message() // no method named `message` found for type `std::io::Error` in the current scope

I don't think there's anything that will get you exactly "Permission denied". The closest I know of is the Display implementation of the Error, which still includes the underlying error code:
use std::fs::File;
use std::error::Error;
fn main() {
let error = File::open("/does-not-exist").unwrap_err();
println!("{:?}", error);
// Error { repr: Os { code: 2, message: "No such file or directory" } }
println!("{}", error);
// No such file or directory (os error 2)
println!("{}", error.description());
// entity not found
}
If this is suitable, you can use error.to_string().
The standard library gets this string from sys::os, which gets defined based on the platform. For example, on UNIX-like platforms, it uses strerror_r. This function does not appear to be exposed in any public fashion, however.

Related

Some errors E0425 & E0599 write_fmt

mod loginfo{
use std::io::Error;
use chrono::prelude::*;
use std::io::prelude::*;
use std::fs::OpenOptions;
const LOG_SYS :&'static str = "log.txt";
const LOG_ERR :&'static str = "log_error.txt";
pub fn set_log_error(info: String)->Result<(), String>{
let mut handler = OpenOptions::new().append(true)
.open(LOG_ERR);
if handler.is_err(){
create_file(LOG_ERR.to_owned()).unwrap();
set_log_error(info).unwrap();
}
if let Err(_errno) = handler.write_fmt(
format_args!("{:?}\t{:?} ->[Last OS error({:?})]\n",
Utc::now().to_rfc2822().to_string(), info,
Error::last_os_error()) ){
panic!(
"\nCannot write info log error\t Info\t:{:?}\n",
Error::last_os_error());
}
Ok(())
}
pub fn set_log(info: String)->Result<(), String>{
let mut handler = OpenOptions::new().append(true)
.open(LOG_SYS);
if handler.is_err(){
set_log_error("Cannot write info log".to_owned())
.unwrap();
}
if let Err(_errno) = handler.write_fmt(
format_args!("{:?}\t{:?}\n",
Utc::now().to_rfc2822().to_string(), info)){
set_log_error("Cannot write data log file".to_owned())
.unwrap();
}
Ok(())
}
pub fn create_file(filename : String)->Result<(), String>{
let handler = OpenOptions::new().write(true)
.create(true).open(filename);
if handler.is_err(){
panic!(
"\nCannot create log file\t Info\t:{:?}\n",
Error::last_os_error());
}
Ok(())
}
}
When compiling, I get the following errors, "error[E0599]: no method named write_fmt found for enum std::result::Result<std::fs::File, std::io::Error> in the current scope --> src/loginfo.rs:19:38`"
but despite using the right imports, I still get the same errors. Is this due to a bad implementation of the module?
Thank you in advance for your answers and remarks?
+1 #Masklinn Ok I think I understand it would be easier to just write
pub fn foo_write_log( info: String){
let mut handler = OpenOptions::new().append(true)
.create(true).open(LOG_SYS).expect("Cannot create log");
handler.write_fmt(
format_args!("{:?}\t{:?} ->[Last OS error({:?})]\n",
Utc::now().to_rfc2822().to_string(), info,
Error::last_os_error())).unwrap();
}
but despite using the right imports, I still get the same errors. Is this due to a bad implementation of the module?
Kind-of? If you look at the type specified in the error, handler is a Result<File, Error>. And while io::Write is implemented on File, it's not implemented on Result.
The problem is that while you're checking whether handler.is_err() you never get the file out of it, nor do you ever return in the error case. Normally you'd use something like match or if let or one of the higher-order methods (e.g. Result::map, Result::and_then) in order to handle or propagate the various cases.
And to be honest the entire thing is rather odd and awkward e.g. your functions can fail but they panic instead (you never actually return an Err); if you're going to try and create a file when opening it for writing fails, why not just do that directly[0]; you are manually calling write_fmt and format_args why not just write!; write_fmt already returns an io::Error why do you discard it then ask for it again via Error::last_os_error; etc...
It's also a bit strange to hand-roll your own logger thing when the rust ecosystem already has a bunch of them though you do you; and the naming is also somewhat awkward e.g. I'd expect something called set_X to actually set the X, so to me set_log would be a way to set the file being logged to.
[0] .create(true).append(true) should open the file in append mode if it exists and create it otherwise; not to mention your version has a concurrency issue: if the open-for-append fails you create the file in write mode, but someone else could have created the file -- with content -- between the two calls, in which case you're going to partially overwrite the file

Detecting a ConnectionReset in Rust, instead of having the Thread panic

So I have a multi-threading program in Rust, which sends Get Requests to my Website, and I'm wondering how I can detect a ConnectionReset.
What I'm trying to do is, after the request, check if there was a ConnectionReset, and if there was, wait for a minute, so the thread doesn't panic
The code I'm using right now
let mut req = reqwest::get(&url).unwrap();
And after that was executed I want to check if there's a ConnectionReset, and then println ("Connection Error"), instead of having the thread panic.
The Error, that I want to detect
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value:
Error { kind: Io(Custom { kind: Other, error: Os { code: 10054, kind: ConnectionReset,
message: "An existing connection was forcibly closed by the remote host." } }),
url: Some("https://tayhay.vip") }', src\main.rs:22:43
I also read something about std::panic::catch_unwind, but I am not sure if that's the right way to go.
.unwrap means literally: "panic in case of error". If you don't want to panic, you will have to handle the error yourself. You have three solutions here depending on code you haven't shown us:
Propagate the error up with the ? operator and let the calling function handle it.
Have some default value ready to use (or create on the fly) in case of error:
let mut req = reqwest::get (&url).unwrap_or (default);
or
let mut req = reqwest::get (&url).unwrap_or_else (|_| { default });
(this probably doesn't apply in this specific case since I don't know what would make a sensible default here, but it applies in other error handling situations).
Have some specific error handling code:
match reqwest::get (&url) {
Ok (mut req) => { /* Process the request */ },
Err (e) => { /* Handle the error */ },
}
For more details, the Rust book has a full chapter on error handling.

Rust Type Inference Error

I'm writing a chat server over TCP as a learning project. I've been tinkering with the ws crate today, but I've come across an issue. This is the code I wrote, modifying their server example.
extern crate ws;
extern crate env_logger;
use ws::listen;
fn main() {
// Setup logging
env_logger::init().unwrap();
// Listen on an address and call the closure for each connection
if let Err(error) = listen("127.0.0.1:3012", |out| {
let mut message: String;
// The handler needs to take ownership of out, so we use move
move |message| {
message = message.trim();
// Handle messages received on this connection
println!("Server got message '{}'. ", message);
// Use the out channel to send messages back
out.send(message)
}
}) {
// Inform the user of failure
println!("Failed to create WebSocket due to {:?}", error);
}
}
When I try compiling it I get an error:
error: the type of this value must be known in this context
--> src/main.rs:15:23
|
15 | message = message.trim();
| ^^^^^^^^^^^^^^
Why is this happening? How may I fix this?
move |message| shadows the message variable you've declared outside the closure. So within the closure.. message is said to be a ws::Message ... except you've done this:
message = message.trim();
The compiler goes "oh no! trim()? That doesn't exist for ws::Message".. and so now it doesn't quite know what to do.
Option 1
The first fix involves delegating the trim() call to the client who sends the message.
The fix is to not make any assumptions about what the message is inside this closure. If you keep this:
move |message|
..but remove the trim() call, the compiler happily infers its type as ws::Message and will build:
if let Err(error) = listen("127.0.0.1:3012", |out| {
// The handler needs to take ownership of out, so we use move
move |message| {
// --- REMOVED trim() call ---
// Handle messages received on this connection
println!("Server got message '{}'. ", message);
// Use the out channel to send messages back
out.send(message)
}
}
This gives you the option of delegating the trim() call to the client instead.
Option 2
Option 2 involves inspecting the type of message you've received, and making sure you trim it only if it is text:
// The handler needs to take ownership of out, so we use move
move |mut message: ws::Message| {
// Only do it if the Message is text
if message.is_text() {
message = ws::Message::Text(message.as_text().unwrap().trim().into());
}
// Handle messages received on this connection
println!("Server got message '{}'. ", message);
// Use the out channel to send messages back
out.send(message)
}
This is perhaps a little more verbose than it needs to be.. but hopefully it shows you what the actual issue is with your original snippet of code.

Check if a command is in PATH/executable as process

I want to execute an external program via std::process::Command::spawn. Furthermore I want to know the reason why spawning the process failed: is it because the given program name doesn't exist/is not in PATH or because of some different error?
Example code of what I want to achieve:
match Command::new("rustc").spawn() {
Ok(_) => println!("Was spawned :)"),
Err(e) => {
if /* ??? */ {
println!("`rustc` was not found! Check your PATH!")
} else {
println!("Some strange error occurred :(");
}
},
}
When I try to execute a program that isn't on my system, I get:
Error { repr: Os { code: 2, message: "No such file or directory" } }
But I don't want to rely on that. Is there a way to determine if a program exists in PATH?
You can use e.kind() to find what ErrorKind the error was.
match Command::new("rustc").spawn() {
Ok(_) => println!("Was spawned :)"),
Err(e) => {
if let NotFound = e.kind() {
println!("`rustc` was not found! Check your PATH!")
} else {
println!("Some strange error occurred :(");
}
},
}
Edit: I didn't find any explicit documentation about what error kinds can be returned, so I looked up the source code. It seems the error is returned straight from the OS. The relevant code seems to be in src/libstd/sys/[unix/windows/..]/process.rs. A snippet from the Unix version:
One more edit: On a second thought, I'm not sure if the licenses actually allows posting parts of Rust sources here, so you can see it on github
Which just returns Error::from_raw_os_err(...). The Windows version seemed more complicated, and I couldn't immediately find where it even returns errors from. Either way, it seems you're at the mercy of your operating system regarding that. At least I found the following test in src/libstd/process.rs:
Same as above: github
That seems to guarantee that an ErrorKind::NotFound should be returned at least when the binary is not found. It makes sense to assume that the OS wouldn't give a NotFound error in other cases, but who knows. If you want to be absolutely sure that the program really was not found, you'll have to search the directories in $PATH manually. Something like:
use std::env;
use std::fs;
fn is_program_in_path(program: &str) -> bool {
if let Ok(path) = env::var("PATH") {
for p in path.split(":") {
let p_str = format!("{}/{}", p, program);
if fs::metadata(p_str).is_ok() {
return true;
}
}
}
false
}
fn main() {
let program = "rustca"; // shouldn't be found
if is_program_in_path(program) {
println!("Yes.");
} else {
println!("No.");
}
}

Why do I get a Bad File Descriptor error when writing to opened File?

Calling write_all on a file returns an error with the description: os error. Debug printing the error outputs: Err(Error { repr: Os(9) })
What does the error mean?
You didn't include any code, so I had to make wild guesses about what you are doing. Here's one piece of code that reproduces your error:
use std::fs;
use std::io::Write;
fn main() {
let mut f = fs::File::open("/").unwrap();
// f.write_all(b"hello").unwrap();
// Error { repr: Os(9) }
match f.write_all(b"hello") {
Ok(..) => {},
Err(e) => println!("{}", e),
}
// Bad file descriptor (os error 9)
}
If you use the Display ({}) format instead of Debug ({:?}), you will see an error message that is nicer than just the error code. Note that unwrap will use the Debug formatter, so you have to use match in this case.
You could also look up the error code in the kernel source. You don't indicate if you are running Windows (unlikely), OS X or Linux, so I guessed Linux.
There are lots of SO questions that then explain what the code can mean, but I'm sure you know how to search through those, now that you have a handle on the problem.

Resources