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
.