Type deduction error when reading from file - file-io

According to multiple sources, I believe this is the correct way to read a string from a file:
use std::error::Error;
fn main() {
let path = std::path::Path::new("input.txt");
let file = match std::fs::File::open(&path) {
Err(e) => {
panic!("Failed to read file {}: {}",
path.display(),
e.description())
}
};
let mut s = String::new();
let mut v = Vec::new();
match file.read_to_string(&mut s) {
Err(e) => panic!("Failed to read file contents: {}", e.description()),
}
println!("{}", s);
}
But this code produces an error using Rust 1.17.0 so I must be missing something:
error: the type of this value must be known in this context
--> src/main.rs:16:11
|
16 | match file.read_to_string(&mut s) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^

You have multiple overlapping issues. Whenever debugging a programming problem, it helps to create a Minimal, Complete Verifiable Example.
Start by commenting out match file.read_to_string(&mut s) { /* ... */ }. Then you will get another error:
error[E0282]: type annotations needed
--> src/main.rs:15:17
|
15 | let mut v = Vec::new();
| ----- ^^^^^^^^ cannot infer type for `T`
| |
| consider giving `v` a type
Comment out that line too, giving:
error[E0004]: non-exhaustive patterns: `Ok(_)` not covered
--> src/main.rs:6:22
|
6 | let file = match std::fs::File::open(&path) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `Ok(_)` not covered
This is your real issue. Result is an enum with two values, Ok and Err. You have to handle all variants in a match.
In this case, it's easiest to use unwrap_or_else:
let file = std::fs::File::open("input.txt").unwrap_or_else(|e| {
panic!(
"Failed to read file {}: {}",
path.display(),
e.description()
)
});
You can remove the unused vector and apply the same unwrap_or_else to the other failure case. You then need to:
Import std::io::Read.
Declare file as mutable.
You can also:
Print an error directly using {}.
Pass a string slice to File::open.
use std::io::Read;
fn main() {
let path = "input.txt";
let mut file = std::fs::File::open(path).unwrap_or_else(|e| {
panic!("Failed to read file {}: {}", path, e);
});
let mut s = String::new();
file.read_to_string(&mut s).unwrap_or_else(|e| {
panic!("Failed to read file contents: {}", e);
});
println!("{}", s);
}
Compare your code against What's the de-facto way of reading and writing files in Rust 1.x? as well.

Related

How can I print data in a way that consumes local variables when an assert fails in Rust?

I have some tests which have some variables that hold some important data and I'd like to print their data when an assertion fails. Getting the data I need consumes the variables, so the printing code must own the variables. In this example, I'd want to call dump_foo_data once an assertion fails:
struct Foo();
fn dump_foo_data(f: Foo) {
eprintln!("Behold, Foo data: ");
}
#[test]
fn my_test() {
let f = Foo();
eprintln!("begin");
// do a test
&f;
let success = true;
assert!(success);
// do another test
&f;
let success = false;
assert!(success);
}
I can make a very bad solution by making dump_foo_data non-returning and panic:
fn dump_foo_data(f: Foo) -> ! {
eprintln!("Behold, Foo data: ");
panic!();
}
Then instead of using assert!, I check the failure with an if and maybe call dump_foo_data:
let success = true;
if !success {
dump_foo_data(f);
}
This is too many lines of code, and I need to specify f. In reality, I have more than one variable like f that I need to dump data from, so it's not very nice to list out single relevant local variable in every check.
I couldn't figure out how to write a macro to make this better because I'd still need to pass every relevant local variable to the macro.
I couldn't think of a way to use std::panic either. update_hook would need to take ownership of f, then I couldn't use it in tests.
Is there any good way to do this in Rust?
Edit: I've thought of another approach: put each relevant local in an Rc then pass each of those to std::panic::update_hook. I've not confirmed whether this'll work yet.
Edit 2: Maybe I could abuse break to do what I explained with goto in a comment.
One way that doesn't use any macro or shared-interior-mutability-reference magic might be to repossess f:
fn check_or_dump(success: bool, f: Foo) -> Foo {
match success {
true => f,
false => panic!("Behold foo data: {:?}", dump_foo_data(f)),
}
}
You use it like this:
let f = Foo();
let success = true;
let f = check_or_dump(success, f);
let success = false;
let f = check_or_dump(success, f);
// and so on.
Here's a solution without macro or interior mutability and that doesn't require you to list all the variables on each check. It is inspired by this answer:
struct Foo();
fn dump_foo_data(_f: Foo) {
eprintln!("Behold, Foo data: ");
}
#[test]
fn my_test() {
let f = Foo();
let doit = || -> Option<()> {
eprintln!("begin");
// do a test
&f;
let success = true;
success.then_some(())?;
// do another test
&f;
let success = false;
success.then_some(())?;
Some(())
};
if let None = doit() {
dump_foo_data (f);
panic!("Test failure");
}
}
Playground
I've worked out a solution using the panic handler:
use std::rc::Rc;
use std::cell::{Cell, RefCell};
use std::panic::PanicInfo;
thread_local! {
static TL_PANIC_TARGETS: RefCell<Vec<Rc<dyn PanicTrigger>>> = RefCell::new(vec![]);
}
pub trait PanicTrigger {
fn panic_trigger(self: Rc<Self>);
}
pub fn register_panic_trigger<P: PanicTrigger + 'static>(p: Rc<P>) {
TL_PANIC_TARGETS.with(|v: _| {
v.borrow_mut().push(p.clone());
});
}
#[ctor::ctor]
fn set_panic_hook() {
let old_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |pi: &PanicInfo| {
run_panic_triggers(pi);
old_hook(pi);
}));
}
fn run_panic_triggers(_: &PanicInfo) {
TL_PANIC_TARGETS.with(|v: _| {
for pt in v.take() {
pt.panic_trigger();
}
});
}
struct Foo();
fn dump_foo_data(_f: Foo) {
eprintln!("Behold, Foo data: ");
}
impl PanicTrigger for Cell<Option<Foo>> {
fn panic_trigger(self: Rc<Self>) {
if let Some(f) = self.take() {
dump_foo_data(f);
}
}
}
#[test]
fn my_test() {
let f = Rc::new(Cell::new(Some(Foo())));
register_panic_trigger(f.clone());
let success = true;
assert!(success);
let success = false;
assert!(success);
}
fn main() { }
Basically, you put the relevant data in an Rc and keep a local reference and put one in TLS for the panic handler. You need to put it in an Option in a Cell so that you can move out of it.
Types that don't need to be owned to print relevant data can be registered too, and you don't need to implement PanicTrigger on a Cell<Option<T>>, just T.
This is thread-safe.
Because the data is so wrapped up, it's harder to manipulate in the test body. But now you can use normal assert!. It's a trade-off.

When I try to parse input from read_line() I get ParseIntError { kind: InvalidDigit }

I am writing a function that gets an initial length of a vector then reads input until the vector is filled. Things go wrong when I convert the input string into an integer.
fn read_to_vector(prompt: &str) -> Vec<String> {
println!("Enter the number of inital values: ");
let length_string:String = read_value();
let length = length_string.parse::<i32>().unwrap();
println!("{}", prompt);
let mut buffer_vector:Vec<String> = Vec::new();
for _i in 1..(length + 1) {
let buffer_str:String = read_value();
buffer_vector.push(buffer_str);
}
return buffer_vector;
}
fn read_value() -> String {
use std::io;
let mut buf:String = String::new();
io::stdin().read_line(&mut buf).expect("Failed to get input");
return buf;
}
Here is the error message:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', src/main.rs:8:47
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
I searched online but I could not find anything related.
read_line() does not trim any whitespace. There is probably a newline character at the end of the string, which indeed is not a digit, and this causes parsing to fail. To fix this, trim whitespace from the end of the string before returning it:
return buf.trim_end().to_string();
To save an allocation, you can combine trim_end() with truncate() on the owned string:
let new_len = buf.trim_end().len();
buf.truncate(new_len);
return buf;

How can I convert a Result<T, E1> to Result<T, E2> when the question mark operator is ineffective?

Is there an idiomatic/concise way to convert Result<T, E1> to Result<T, E2> when E2 has implemented the From trait for E1?
I cannot use the ? operator because it doesn't compile.
In my case, E1 is ParseIntError, and E2 is a custom CalcPackageSizeError error enum.
playground
use std::error;
use std::fmt;
use std::io;
use std::io::Read;
use std::num::ParseIntError;
#[derive(Debug)]
enum CalcPackageSizeError {
InvalidInput(&'static str),
BadNum(&'static str),
}
impl error::Error for CalcPackageSizeError {}
impl fmt::Display for CalcPackageSizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
Self::InvalidInput(err_desc) => err_desc,
Self::BadNum(err_desc) => err_desc,
}
)
}
}
impl From<ParseIntError> for CalcPackageSizeError {
fn from(_: ParseIntError) -> Self {
CalcPackageSizeError::BadNum(
"Error in calculating size of one or more of the packages involved.",
)
}
}
fn parse_comma_separated_num(num_str: &str) -> Result<usize, ParseIntError> {
num_str
.chars()
.filter(|char| *char != ',')
.collect::<String>()
.parse::<usize>()
}
fn calc_all_package_size(contents: &str) -> Result<usize, CalcPackageSizeError> {
contents
.split('\n')
.skip(2)
.map(|package_str| {
let amount_str = package_str
.split(' ')
.filter(|element| *element != "")
.nth(1);
if let Some(amt_str) = amount_str {
parse_comma_separated_num(amt_str)?
// match parse_comma_separated_num(amt_str) {
// Ok(amt) => Ok(amt),
// Err(err) => Err(From::from(err)),
// }
} else {
Err(CalcPackageSizeError::InvalidInput("Input not as expected, expected the 2nd spaces-delimited item to be the size (integer)."))
}
})
.sum()
}
fn main() {
let mut wajig_input = String::from(
"Package Size (KB) Status
=================================-==========-============
geoip-database 10,015 installed
aptitude-common 10,099 installed
ieee-data 10,137 installed
hplip-data 10,195 installed
librsvg2-2 10,412 installed
fonts-noto-color-emoji 10,610 installed",
);
// io::stdin().read_to_string(&mut wajig_input).expect("stdin io rarely fails.");
match calc_all_package_size(wajig_input.as_str()) {
Ok(total_size_in_kb) => {
let size_in_mb = total_size_in_kb as f64 / 1024.0;
println!("Total size of packages installed: {} MB", size_in_mb);
}
Err(error) => {
println!("Oops! Encountered some error while calculating packages' size.");
println!("Here's the error: \n {}", error);
println!("\n-- Gracefully exiting..");
}
}
}
This gives a compile error:
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:59:17
|
52 | / if let Some(amt_str) = amount_str {
53 | | parse_comma_separated_num(amt_str)?
| | ----------------------------------- expected because of this
54 | | // match parse_comma_separated_num(amt_str) {
55 | | // Ok(amt) => Ok(amt),
... |
59 | | Err(CalcPackageSizeError::InvalidInput("Input not as expected, expected the 2nd spaces-delimited item to be the size (integer)."))
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found enum `Result`
60 | | }
| |_____________- `if` and `else` have incompatible types
|
= note: expected type `usize`
found enum `Result<_, CalcPackageSizeError>`
note: return type inferred to be `usize` here
--> src/main.rs:53:17
|
53 | parse_comma_separated_num(amt_str)?
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Though both errors seem semantically similar, I need to respond in different way to both the situations, so I can't make them one.
Use a combination of Result::map_err and Into::into:
if let Some(amt_str) = amount_str {
parse_comma_separated_num(amt_str).map_err(Into::into)
} else {
Err(CalcPackageSizeError::InvalidInput("Input not as expected, expected the 2nd spaces-delimited item to be the size (integer)."))
}
As Jmb points out in the comments, you could also use Ok and ? together:
if let Some(amt_str) = amount_str {
Ok(parse_comma_separated_num(amt_str)?)
} else {
Err(CalcPackageSizeError::InvalidInput("Input not as expected, expected the 2nd spaces-delimited item to be the size (integer)."))
}
The problem is that ? unwraps the value on success and returns from the function on failure. That means that parse_comma_separated_num(amt_str)? evaluates to a usize, as the compiler tells you:
return type inferred to be usize here
This would cause the first block to evaluate to a usize and the second block to evaluate to a Result. Those aren't the same type, resulting in the error you got.
Converting the error type using map_err preserves the value as a Result, allowing both blocks to evaluate to the same type.
See also:
Rust proper error handling (auto convert from one error type to another with question mark)

Return a Result from a for loop or nothing if there are no results

I want to return the Result as shown in below from the for loop. Please help which would be the best way solve this error. I tried the pattern matching with returning None which works. But I need to return Error.
pub fn get_account(&self) -> Result<Keys, Error> {
//PATH is default home directory
let values = match load_json_file(PATH + "/keys.json") {
Ok(account) => Ok(account),
Err(e) => {
return Err(Error::Invalid_Tx(
"The sender address cannot be nil".to_owned(),
))
}
};
let accounts: Vec<Keys> = values.unwrap();
let sender_address = self.sender.unwrap();
for acc in accounts {
if acc.address == sender_address {
return Ok(acc);
};
};
Ok(())
}
expected struct commands::key::Keys, found ()rustc(E0308)
You are trying to return two different types from the same function:
line 15: Ok(acc) is of type Result<Keys, Error>
line 18: Ok(()) has type Result<(), Error>
If "no result" is a valid return value, then you can change the function signature to:
pub fn get_account(&self) -> Result<Option<Keys>, Error>;
And then modify those return values to be Ok(Some(acc)) and Ok(None) respectively.
If "no result" is an error then you need to modify the Error type to include this variant. For example:
enum Error {
Invalid_Tx(String),
NotFound,
}
And return Err(Error::NotFound) at the end.
You can also tidy this function up a lot, by using thiserror, which is a popular crate for defining error types:
use thiserror::Error; // thiserror = "1.0.21"
#[derive(Debug, Error)]
enum Error {
#[error("The sender address cannot be nil")]
InvalidTx,
#[error("The key was not found")]
NotFound,
}
pub fn get_account(&self) -> Result<Keys, Error> {
let accounts: Vec<Keys> = load_json_file(PATH + "/keys.json")
.map_err(|_| Error::InvalidTx)?;
let sender_address = self.sender.unwrap();
accounts
.into_iter()
.find(|acc| acc.address == sender_address)
.ok_or(Error::NotFound)
}
This is better because the Strings in the errors do not need to be allocated, but they are still available as static string slices if they are needed for display. I also got rid of the for loop altogether, which makes the function shorter and cleaner.

How to read a C struct from a binary file in Rust? [duplicate]

This question already has answers here:
How to read a struct from a file in Rust?
(3 answers)
Closed 6 years ago.
I have read How to read a struct from file in Rust?, but std::slice::raw::mut_buf_as_slice has been deprecated, hence I want to re-ask this question.
I want to read a struct utmp from /var/run/utmp, and I have tried the following code:
fn read_utmp() -> Utmp {
let mut reader = BufReader::new(File::open("/var/run/utmp").unwrap());
let mut ut = vec![];
let size = mem::size_of::<Utmp>();
reader.take(size as u64).read_to_end(&mut ut);
unsafe {
std::mem::transmute(ut)
}
}
And as expected, the compiler complaint:
error: transmute called with differently sized types: std::vec::Vec (192 bits) to Utmp (3056 bits) [E0512]
How could I do this?
I believe that raw::mut_buf_as_slice was replaced with slice::from_raw_parts_mut.
Note that the following code does not take into account any endianness or padding issues and is intended to be used with POD types. struct utmp should be safe in this case.
Here is a function that can read a struct (of a pod type) from a file:
use std::io::{self, BufReader, Read};
use std::fs::{self, File};
use std::path::Path;
use std::slice;
fn read_struct<T, R: Read>(mut read: R) -> io::Result<T> {
let num_bytes = ::std::mem::size_of::<T>();
unsafe {
let mut s = ::std::mem::uninitialized();
let mut buffer = slice::from_raw_parts_mut(&mut s as *mut T as *mut u8, num_bytes);
match read.read_exact(buffer) {
Ok(()) => Ok(s),
Err(e) => {
::std::mem::forget(s);
Err(e)
}
}
}
}
// use
// read_struct::<Utmp>(reader)
If you want to read all utmp structs from the utmp file, you can execute read_struct multiple times or read all the file at once:
fn read_structs<T, P: AsRef<Path>>(path: P) -> io::Result<Vec<T>> {
let path = path.as_ref();
let struct_size = ::std::mem::size_of::<T>();
let num_bytes = try!(fs::metadata(path)).len() as usize;
let num_structs = num_bytes / struct_size;
let mut reader = BufReader::new(try!(File::open(path)));
let mut r = Vec::<T>::with_capacity(num_structs);
unsafe {
let mut buffer = slice::from_raw_parts_mut(r.as_mut_ptr() as *mut u8, num_bytes);
try!(reader.read_exact(buffer));
r.set_len(num_structs);
}
Ok(r)
}
// use
// read_structs::<Utmp, _>("/var/run/utmp"))