Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Result getting unexpected type argument

Tags:

rust

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.

System Details:

  • macOS 10.13.6
  • rustc 1.36.0 (a53f9df32 2019-07-03)
  • cargo 1.36.0 (c4fcfb725 2019-05-15)
like image 599
C.Nivs Avatar asked Sep 04 '19 19:09

C.Nivs


People also ask

How to generate error when arguments are passed to a class?

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 ())

What is a **kwargs argument in Python?

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,

What are keyword arguments in Python?

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.


2 Answers

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:

  • You can convert your entire error chain to another type (explicitly or by taking advantage of into() casts)
  • You can make your own errors return std::io::Error instances

There 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 :-)

like image 127
Sébastien Renauld Avatar answered Sep 28 '22 01:09

Sébastien Renauld


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.

like image 26
turbulencetoo Avatar answered Sep 28 '22 01:09

turbulencetoo