Expected type parameter `A`, found `&str` in trait method

Trying to create a method in a trait that use general type parameter, and got an error, did not understand what the compiler means by type parameters must be constrained to match other types

My code is:

use std::hash::Hash;
use std::collections::HashMap;
use crate::modules::models::transactions::{Transaction, TransactionBuffer};
use crate::modules::models::inventory::Inventory;
use std::path::Path;

pub(crate) struct Inventory<'a, Warehouse: Eq + Hash, Item: Eq + Hash> {
    pub(crate) table: HashMap<Warehouse, HashMap<Item, Transaction<'a>>>
}

pub(crate) trait Memory<'a, A: Eq + Hash, B: Eq + Hash> {
    fn get(&self, a: &A, b: &B) -> Option<&Transaction<'a>>;

    fn set(&mut self, a: A, b: B, v: Transaction<'a>);
}

impl<'a, A: Eq + Hash, B: Eq + Hash> Inventory<'a, A, B> {
    pub(crate) fn new() -> Inventory<'a, A, B> {
        Inventory {
            table: HashMap::new(),
        }
    }

    pub(crate) fn collect(&mut self, file: &str, records: &'a mut Vec<TransactionBuffer>) {
        let file_path = Path::new(file);
        let mut rdr = csv::ReaderBuilder::new()
            .has_headers(true)
            .delimiter(b',')
            .from_path(file_path)
            .unwrap();
        for result in rdr.records() {
            records.push(result.unwrap().deserialize(None).unwrap());
        }
        for record in &*records {
            let trx = Transaction {
                warehouse: &record.warehouse,
                movement: &record.movement,
                item: &record.item,
                date: &record.date,
                batch: &record.batch,
                quantity: record.quantity
            };
            self.set(record.warehouse.as_str(), record.item.as_str(), trx);
        }
    }
}

impl<'a, A: Eq + Hash, B: Eq + Hash> Memory<'a, A, B> for Inventory<'a, A, B> {
    fn get(&self, a: &A, b: &B) -> Option<&Transaction<'a>> {
        self.table.get(a)?.get(b)
    }

    fn set(&mut self, a: A, b: B, v: Transaction<'a>) {
        let inner = self.table.entry(a).or_insert(HashMap::new());
        inner.insert(b, v);
    }
}

And getting this error:

error[E0308]: mismatched types
  --> src\modules\traits\inventory.rs:39:22
   |
13 | impl<'a, A: Eq + Hash, B: Eq + Hash> Inventory<'a, A, B> {
   |          - this type parameter
...
39 |             self.set(record.warehouse.as_str(), record.item.as_str(), trx);
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `A`, found `&str`
   |
   = note: expected type parameter `A`
                   found reference `&str`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

error[E0308]: mismatched types
  --> src\modules\traits\inventory.rs:39:49
   |
13 | impl<'a, A: Eq + Hash, B: Eq + Hash> Inventory<'a, A, B> {
   |                        - this type parameter
...
39 |             self.set(record.warehouse.as_str(), record.item.as_str(), trx);
   |                                                 ^^^^^^^^^^^^^^^^^^^^ expected type parameter `B`, found `&str`
   |
   = note: expected type parameter `B`
                   found reference `&str`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

error: aborting due to 2 previous errors; 2 warnings emitted

Knowing that before my trial in creating the collect method, set method worked smoothly as in the below code:

fn main() {
    let mut inv: Inventory<&str, &str> = Inventory::new();
    let mut inv_holding: Vec<TransactionBuffer> = Vec::new();
    let input_file = "data/transactions.csv";
 //   inv.collect(input_file, inv_holding.as_mut());
    let t = Transaction {
        warehouse: "",
        movement: "",
        item: "A",
        date: "1/1/2020",
        batch: "A-1",
        quantity: 100
    };

    inv.set("x", "y", t);
    println!("t: {:#?}", inv.get(&"x", &"y")); */
}
1 Like

Your implementation assumes that A is a string, if A or B where u32s this code wouldn't make sense:

self.set(record.warehouse.as_str(), record.item.as_str(), trx);

When you use a generic parameter, you can't choose what type it is. You're letting the caller of the method choose what type they want for A, and it can be any type. They could say A is SomeStructThatImplementsA, and then it'd be a huge error to try to assign &str to SomeStructThatImplementsA.

But none of them is u32, both are &str

Once I removed the .as_str() I got the same error msg, but telling "expected type parameter 'A', found strict 'std::string::String'

You've constrained A and B to have impls for Eq and Hash but you haven't constrained either one to be a string or to implement a trait that will convert them to a string. Rust is telling you that you haven't constrained them sufficiently: so that the required string operators are implemented for all A and all B that meet the constraints that you specified.

mm, so how can I constrain them sufficiently to accept String or &str

Add AsRef<str> as a constraint

Same result/error.

Did yu add it to each of the six constraints on A and B?

Yes, everywhere I've Ed + Hash

You'll also need to change the code to use .as_ref() instead of .as_str() if you do this.

Same, this time gave me:

error[E0308]: mismatched types
  --> src\modules\traits\inventory.rs:39:22
   |
13 | impl<'a, A: Eq + AsRef<str> + Hash, B: Eq + AsRef<str> + Hash> Inventory<'a, A, B> {
   |          - this type parameter
...
39 |             self.set(record.warehouse.as_ref(), record.item.as_ref(), trx);
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `A`, found reference
   |
   = note: expected type parameter `A`
                   found reference `&_`

BTW: AsRef<str> is a very poor choice here, because as_ref() gives you a temporary borrowed value that you aren't allowed to keep, so your attempts at having a temporary value in long-lived hashset will fail.

Usually A: Into<String> is used in such cases, and self.set(record.warehouse.into(), … should work.

No, that's the point - they aren't &str, they're A and B. You don't chose the types. The caller of this method chooses. It's valid to call Inventory::<u32,u32>::collect() and then your code will try to assign &str to u32.

You can't make collect() turn it into Inventory::<&str,&str>::collect() from inside. You could use &str specifically only if you change your implementation from generic impl Inventory<'a, A, B> to non-generic impl Inventory<&str, &str>.

It gave me:

error[E0277]: the trait bound `A: std::convert::From<std::string::String>` is not satisfied
  --> src\modules\traits\inventory.rs:39:39
   |
39 |             self.set(record.warehouse.into(), record.item.into(), trx);
   |                                       ^^^^ the trait `std::convert::From<std::string::String>` is not implemented for `A`
   |
   = note: required because of the requirements on the impl of `std::convert::Into<A>` for `std::string::String`
help: consider further restricting this bound
   |
13 | impl<'a, A: Eq + Into<String> + Hash + std::convert::From<std::string::String>, B: Eq + Into<String> + Hash> Inventory<'a, A, B> {
   |                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Once I add + std::convert::From<std::string::String> it gave me:

error[E0507]: cannot move out of `record.warehouse` which is behind a shared reference
  --> src\modules\traits\inventory.rs:41:22
   |
41 |             self.set(record.warehouse.into(), record.item.into(), trx);
   |                      ^^^^^^^^^^^^^^^^ move occurs because `record.warehouse` has type `std::string::String`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `record.item` which is behind a shared reference
  --> src\modules\traits\inventory.rs:41:47
   |
41 |             self.set(record.warehouse.into(), record.item.into(), trx);
   |                                               ^^^^^^^^^^^ move occurs because `record.item` has type `std::string::String`, which does not implement the `Copy` trait

I tried implementing Copy as below but same error remained:

#[derive(Debug, Default, Copy, Clone, Deserialize)]
pub(crate) struct Transaction<'a> {
    pub(crate) warehouse: &'a str,
    pub(crate) movement: &'a str,
    pub(crate) item: &'a str,
    pub(crate) date: &'a str,
    pub(crate) batch: &'a str,
    pub(crate) quantity: i64
}

for Into<String> to work you need to change:

pub(crate) table: HashMap<Warehouse, HashMap<Item, Transaction<'a>>>

to

pub(crate) table: HashMap<String, HashMap<Item, Transaction<'a>>>

Why do you make Warehouse generic, when your code doesn't want it to be generic?


Alternatively, you can keep set supporting only A type, and specify:

A: From<&str>

and then use:

set(A::from(record.warehouse),


The important thing to understand is that generics are not a flexible "any" type, like Object in Java that you can assign whatever you want to.

They are very inflexible. They require the type parameter to be one specific exact type, and your code is not even allowed to know what type that is! Your code can only treat that type as a black box, as a type that is incompatible with all other types, and as a type that can't do anything, except what you've required from it via trait bounds. That type could be anything, even a type that doesn't exists yet! Someone in the future will be able to create a new type, implement necessary traits on their type, and your generic has to already support that type.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.