Home > Software engineering >  return of generic when trait is not implemented
return of generic when trait is not implemented

Time:11-02

Here is the code I've been struggling with for a while

use ethers_providers::{Provider, Http}; //, JsonRpcClient};

// this doesn't work
// trait ClientGeneric<T> {
//     fn get_client(&self, url:&str) -> Provider<T> {
//         Provider::<T>::try_from(url).unwrap()
//     }
// }
// this works but I want generic
trait ClientConcrete {
    fn get_client(&self, url:&str) -> Provider<Http> {
        Provider::<Http>::try_from(url).unwrap()
    }
}
struct Test;
impl ClientConcrete for Test {}

#[cfg( test )]
mod tests {
    use super::*;
    // #[test]
    // fn test_get_client_generic() {
    //     let test = Test{};
    //     let client = test.get_client("http://localhost:8080");
    // }
    #[test]
    fn test_get_client_concrete() {
        let test = Test{};
        let client = test.get_client("http://localhost:8080");
    }
}

I want to return Provider<T> for every single transport T: http, ws, ipc, mock, etc Generally every transport has a TryFrom trait implemented. However the code above won't work with generic T even though it works with concrete transports. The error I get for generic T:

error[E0277]: the trait bound `Provider<T>: From<&str>` is not satisfied
 --> src/test.rs:6:9
  |
6 |         Provider::<T>::try_from(url).unwrap()
  |         ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<&str>` is not implemented for `Provider<T>`
  |
  = help: the following other types implement trait `TryFrom<T>`:
            <Provider<Http> as TryFrom<&'a String>>
            <Provider<Http> as TryFrom<&str>>
            <Provider<Http> as TryFrom<String>>
  = note: required because of the requirements on the impl of `Into<Provider<T>>` for `&str`
  = note: required because of the requirements on the impl of `TryFrom<&str>` for `Provider<T>`

Unfortunately there is no match statement over type T I could use (as in Golang) to go to concrete types implementations. As I understand I can not implement foreign trait TryFrom<&str> for foreign struct Provider<T>. TryFrom<&str> is implemented for underlying types Http, Ws, Ipc, etc

Is there anything else I could do?

CodePudding user response:

This is your generic function that doesn't compile:

fn get_client(&self, url:&str) -> Provider<T> {
    Provider::<T>::try_from(url).unwrap()
}

The reason it does not compile is due to T not having any bounds specified. If this compiled, it would mean the function could create a Provider<T> for any T. But that's not possible; as you note only certain types like Http work for T, because Provider<Http> implements TryFrom<&str>. So you need to provide these necessary bounds so the function can compile.

This is how I'd write it:

use std::fmt::Debug;

use ethers_providers::Provider;

trait ClientGeneric<T>
where
    for<'a> &'a str: TryInto<Provider<T>>,
    for<'a> <&'a str as TryInto<Provider<T>>>::Error: Debug,
{
    fn get_client(&self, url: &str) -> Provider<T> {
        url.try_into().unwrap()
    }
}

impl<T, U> ClientGeneric<U> for T
where
    for<'a> &'a str: TryInto<Provider<U>>,
    for<'a> <&'a str as TryInto<Provider<U>>>::Error: Debug,
{
}

struct Test;

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_get_client_generic() {
        let test = Test {};
        let _client = test.get_client("http://localhost:8080");
    }
}

Note I opted to use TryInto instead of TryFrom, though either would work fine in this case.

The first bound lets us call try_into() to convert the &str into a (Result-wrapped) Provider<T>:

for<'a> &'a str: TryInto<Provider<T>>

The second bound is just needed so the .unwrap() method works:

for<'a> <&'a str as TryInto<Provider<T>>>::Error: Debug

In real code you should probably avoid unwrap though, and instead return a Result.

  • Related