I am attempting to read values from a file in order to create a struct, and I'm getting a weird pair of errors. A super basic implementation of my code:
extern crate itertools;
use itertools::Itertools;
use std::io::{self, prelude::*, BufReader};
use std::fs::{self, File};
// The struct I will unpack into
struct BasicExample {
a: String,
b: String,
c: String,
d: String,
}
impl BasicExample {
pub fn new(a: String, b: String, c: String, d: String} -> Self {
BasicExample {
a, b, c, d
}
}
// I'm expecting that reading from the config file might fail, so
// I want to return a Result that can be unwrapped. Otherwise an Err
// will be returned with contained value being &'static str
pub fn from_config(filename: &str) -> io::Result<Self, &'static str> {
let file = File::open(filename).expect("Could not open file");
// read args into a Vec<String>, consuming file
let args: Vec<String> = read_config(file);
// I transfer ownership away from args here
let params: Option<(String, String, String, String)> = args.drain(0..4).tuples().next();
// Then I want to match and return, I could probably do if-let here
// but I don't have my hands around the base concept yet, so I'll
// leave that for later
match params {
Some((a, b, c, d)) => Ok(BasicExample::new(a, b, c, d)),
_ => Err("Could not read values into struct")
}
}
fn read_config(file: File) -> Vec<String> {
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
}
}
Running cargo check
to make sure I didn't miss anything, I get the following error:
error[E0107]: wrong number of type arguments: expected 1, found 2
--> src/lib.rs:37:60
|
37 | pub fn from_config(filename: &str) -> io::Result<Self, &'static str> {
| ^^^^^^^^^^^^ unexpected type argument
error: aborting due to previous error
For more information about this error, try `rustc --explain E0107`.
Seems a bit odd. io::Result
should take <T, E>
, and I've given it E
, so let's remove that type argument and see what happens:
error[E0308]: mismatched types
--> src/lib.rs:54:22
|
54 | _ => Err("Could not read values into AzureAuthentication struct"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::io::Error`, found reference
|
= note: expected type `std::io::Error`
found type `&'static str`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
For some reason it is really not happy with the E
I provided. I'm a complete beginner with rust, so maybe I'm just not sure what I'm looking at. What am I doing wrong here? The itertools
ownership trick was borrowed (ha) from this wonderful answer.
The same error can also be generated when unexpected keyword arguments are passed to a class object. For example, class Add (): def __init__ (self, a1, a2): self.a1 = a1 self.a2 = a2 def add_func (self): return self.a1+self.a2 # Initializing the class s = Add (a1=3, a3=4) #call the function add_func () on the class print (s.add_func ())
The **kwargs argument (which stands for k ey w ord arg ument s) is a special argument that can be used in Python to pass various arguments to a Python function. The functions turns unexpected keyword arguments into a dictionary that can be accessed inside the function. Let’s see an example,
Keyword arguments (also called named arguments) are arguments passed into a function with specific parameter names. These names are used to identify them inside a function.
This is actually a super basic error, but one that looks arcane until you get to know (and love) std::io
.
In short, std::result::Result
(the result you know) !== std::io::Result
. The documentation for the first is here, while the second is here
You'll notice on the second one that it is actually a type alias to Result<T, std::io::Error>
. What this means is that it is effectively shorthand for that, where your error case is an instance of std::io::Error
.
As a result, your code is incorrect when you are attempting to just Err()
it with a string slice (since the slice is not std::io::Error
, evidently).
There are multiple ways to fix this:
into()
casts)std::io::Error
instancesThere are valid cases for both options, which is why I'm mentioning both. The second is done relatively easily, like so (full paths are there for documentation purposes). Suppose you're returning an error which matches an entity not found. You would be able to do it like so:
`Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Could not read values into AzureAuthentication struct"))`
There is, however, a better way for your function:
pub fn from_config(filename: &str) -> io::Result<Self> {
let file = File::open(filename)?;
let args: Vec<String> = read_config(file); // This has no error possibility
let params: Option<(String, String, String, String)> = args.drain(0..4).tuples().next();
params.ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "Could not read values into struct")).map(|(a, b, c, d)| BasicExample::new(a,b,c,d))
}
This removes all the indirection from your method and neatly folds away the error types, one by one, so you don't have to worry about them. The Option
gets turned into a Result
thanks to ok_or
, and all is well in the best of worlds :-)
A common pattern in Rust is if your module uses a lot of Result<T, ModuleSpecificErrorType>
, then you can make a custom Result<T>
that abstracts out the error type. This custom type has one fewer generic parameter because the error type is hardcoded.
A std::io::Result<T>
is an abstraction over std::result:Result<T, std::io::Error>
.
See the documentation for io::Result
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With