These are chat archives for rust-lang/rust

22nd
Apr 2019
David Vo
@auscompgeek
Apr 22 03:29
you could've simply passed in self there, since it's already a mutable reference
Pascal Precht
@PascalPrecht
Apr 22 09:06

Hey everyone,

I keep running into a certain case that I don't know how to implement in Rust due to types.

The scenario is simple (in dynamically typed languages): Based on a certain condition, I want create an instance of something that' either one or another type, however, both types implement a certain trait and therefore have the same expected API:

// Foo and Bar both implement a trait that I expect to work with `my_thing`
let my_thing = match some_value {
  "foo" => Foo::new(),
  "bar" => Bar::new(),
}

Rust doesn't let me do this obviously, because the match arms are different types. I did some research and found out about Any and downcasting. However I'm also reading that this only works with 'static types and should be generally avoided. Another comment mentioned that this is usually something that could be solved with an enum.

So i thought I could do this:

enum SomeEnum {
  Foo(Foo)
  Bar(Bar)
}

let my_thing = match some_value {
  "foo" => SomeEnum::Foo(Foo::new()),
  "bar" => SomeEnum::Bar(Bar::new()),
}

This seems to work (yay!). my_thing is now of type SomeEnum. However, in order to work with it I'll have to access the inner values. In fact, I want to pass that inner value to another struct instantiation, which receives a value that implements that trait that both Foo and Bar are implementing:

let other_thing = match my_thing {
  SomeEnum::Foo(foo) => OtherThing::new(foo),
  SomeEnum::Bar(bar) => OtherThing::new(bar)
};

This however, still doesn't compile because Rust infers the type of foo, notices that it's Foo and therefore expects bar to be Foo as well.

Notice that Otherthing::new() works totally fine with Foo and Bar when created imperatively:

let foo = Foo::new();
let other_thing = OtherThing::new(foo);

let bar = Bar::new();
let other_thing = OtherThing::new(bar);

Any body an idea, or pointer how to get around this? Let me know if you need more information!

Denis Lisov
@tanriol
Apr 22 09:08
If you're working off enums, you can make impl MyTrait for SomeEnum that just forwards calls to the inner value.
Pascal Precht
@PascalPrecht
Apr 22 09:09
Thanks for the quick response! Will that make the compiler happy though? foo will still be Foo and bar will still be Bar
Denis Lisov
@tanriol
Apr 22 09:11
You'll be able to call OtherThing::new(my_thing) without matching on it :-)
Pascal Precht
@PascalPrecht
Apr 22 09:11
Ah I see, you'd saying I would then pass the enum instance to Otherthing::new()
got it
Okay, that makes sense... is that the way to go? Or is that just one option. (Trying to learn here haha)
Denis Lisov
@tanriol
Apr 22 09:12
This is the enum-based option. There is also the way based on trait objects with different tradeoffs.
Pascal Precht
@PascalPrecht
Apr 22 09:12
Can you elaborate?
Denis Lisov
@tanriol
Apr 22 09:22
A trait object (dyn MyTrait) is a way of saying "anything that implements MyTrait". This is an unsized type, so it comes with some limitations. Namely, (without complex trickery) it always has to be behind something pointer-like: &dyn Trait, &mut dyn Trait, Box<dyn Trait>, Arc<dyn Trait> and some others. Another limitation is that there are restrictions on the trait itself - if the trait uses certain features, it is "not object-safe" and cannot be used via trait objects.
Pascal Precht
@PascalPrecht
Apr 22 09:25

Ah right, I think I've used that approach in another case where I ended up doing something like:

let my_thing: GeneralType = match value {
  "foo" => GeneralType::new(Box::new(Foo::new())),
  "bar" => GeneralType::new(Box::new(Bar::new())),
};

Something like that..

I'm trying to implement that trait in question for my enum now, so it just delegates calls to the inner instance. But now I'm running into another problem.

That trait introduces a type:

impl Transport for SomeTransport {
  type Out = ???
}

Based on what the inner value/type of SomeTransport is, Out has to change as well.

Is this even possible to implement?

Denis Lisov
@tanriol
Apr 22 09:38
If the inner types have different Out types, you may need to introduce a enum for the Out type too :-(
Pascal Precht
@PascalPrecht
Apr 22 09:41
oh wow.. that's a lot of ceremony..
I must be missing something here..

look here, I tried another approach (this is the concrete code).. every match arm returns web3::Web3:

    let web3 = match config.protocol.parse() {
      Ok(ConnectionProtocols::Http) => {
        let (_, transport) = web3::transports::Http::new().unwrap();
        web3::Web3::new(transport)
      },
      Ok(ConnectionProtocols::Ws) => {
        let (_, transport) = web3::transports::WebSocket::new().unwrap();
        web3::Web3::new(transport)
      },
      Err(err) => Err(err)?,
    };

However, Rust is still complaining because transport once is Http and once Websocket

The thing is I know those are either Http or Websockt, but Web3 is totally capable of that..

Denis Lisov
@tanriol
Apr 22 09:42
This looks like a weird use case. Why do you want to make an OtherThing with something implementing MyTrait without even knowing what MyTrait returns?
Pascal Precht
@PascalPrecht
Apr 22 09:44
web::Web3::new() takes anything that implements Transport. Both transport types used above, Http and Ws, implement that. These are built-in types from the web3 library. I just want to use them interchangebly.
For more context, this is the exact error:
error[E0308]: match arms have incompatible types
  --> /Users/pascalprecht/projects/vibranium/src/blockchain/connector/mod.rs:72:9
   |
65 |       let web3 = match config.protocol.parse() {
   |  ________________-
66 | |       Ok(ConnectionProtocols::Http) => {
67 | |         let (_, transport) = web3::transports::Http::new(&format!("{}://{}:{}", config.protocol, config.host, config.port)).unwrap();                                     
68 | |         web3::Web3::new(transport)
   | |         -------------------------- this is found to be of type `_`
...  |
72 | |         web3::Web3::new(transport)
   | |         ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `web3::transports::Http`, found struct `web3::transports::WebSocket`                                                   
73 | |       },
74 | |       Err(err) => Err(err)?,
75 | |     };
   | |_____- `match` arms have incompatible types
   |
   = note: expected type `web3::Web3<web3::transports::Http>`
              found type `web3::Web3<web3::transports::WebSocket>`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.
error: Could not compile `vibranium`.

To learn more, run the command again with --verbose.

Notice the

expected struct `web3::transports::Http`, found struct `web3::transports::WebSocket`

I'm sure the compiler is right. But I also know that web3::Web3::new() works with both of these types.

Do you think the Enum approach is still the way to go?

Denis Lisov
@tanriol
Apr 22 09:48
Do you expect to need other transports too?
Also, is this code going to be performance-critical?
Pascal Precht
@PascalPrecht
Apr 22 09:48
There's two other transport types but I'm going just with the two for now.
Depends on what performance-critical means. Obviously, software should be fast. But this part is really just about getting an instance of Web3.
I can also try the Box approach I mentioned earlier, but that's also a lot of boilerplate just to get an instance of effectively the same type..
Denis Lisov
@tanriol
Apr 22 09:50
Ok, let's assume it's not... small overheads will likely not be significant compared to the actual RPC.
Pascal Precht
@PascalPrecht
Apr 22 09:51
Okay
Denis Lisov
@tanriol
Apr 22 09:53
Then your transport-enum's Out type should be Box<dyn Future<Item = serde_json::Value, Error = web3::Error>> (and send should box the futures into it)
Pascal Precht
@PascalPrecht
Apr 22 09:54
Oh boy
Okay so that means, I should still stick to the enum solution, implement the Transport trait for it
Denis Lisov
@tanriol
Apr 22 09:54
That's what I'd do.
Pascal Precht
@PascalPrecht
Apr 22 09:55
Okay, I'll give that a spin.
I'll still need to look into what those change actually do , in case it works haha

What do you mean by

and send should box the futures into it

Pascal Precht
@PascalPrecht
Apr 22 10:07
Oh man this is turning out to be really hard..
Even Call in Transport I can't implement. Internally web3 uses web3::rpc::Call. I can't use it because web3::rpc is private
I think I'll move to some dirty solution that's less flexible (not even sure I'll manage to do that haha). But it can't be that it's such a hassle to create an instance of a thing
Denis Lisov
@tanriol
Apr 22 10:09
Not a big hassle :-)
Pascal Precht
@PascalPrecht
Apr 22 10:19
Either way, I appreciate your help here @tanriol ! Thanks for leaving your thoughts on this.
Will have to run to catch a train now, but will continue after that. Maybe I'll make it work haha
Denis Lisov
@tanriol
Apr 22 10:21
Given some imports, it looks like this
#[derive(Debug, Clone)]
pub enum TransportEnum {
    Ws(WebSocket),
    Http(Http),
}

impl Transport for TransportEnum {
    type Out = Box<dyn Future<Item = serde_json::Value, Error = web3::Error>>;

    fn prepare(&self, method: &str, params: Vec<serde_json::Value>) -> (RequestId, Call) {
        match self {
            TransportEnum::Ws(inner) => inner.prepare(method, params),
            TransportEnum::Http(inner) => inner.prepare(method, params),
        }
    }

    fn send(&self, id: RequestId, request: Call) -> Self::Out {
        match self {
            TransportEnum::Ws(inner) => Box::new(inner.send(id, request)),
            TransportEnum::Http(inner) => Box::new(inner.send(id, request)),
        }
    }
}
Pascal Precht
@PascalPrecht
Apr 22 10:22
Ha! That's pretty much exactly what I have too! :D
#[derive(Debug)]
enum Web3Transports {
  Http((web3::transports::EventLoopHandle, web3::transports::Http)),
  Ws((web3::transports::EventLoopHandle, web3::transports::WebSocket)),
}

impl Transport for Web3Transports {
  type Out = Box<dyn Future<Item = serde_json::Value, Error = web3::Error>>;
  fn prepare(&self, method: &str, params: Vec<serde_json::Value>) -> (RequestId, rpc::Call) {
    match self {
      Web3Transports::Http((_, transport)) => transport.prepare(&method, params),
      Web3Transports::Ws((_, transport)) => transport.prepare(&method, params)
    }
  }

  fn send(&self, id: RequestId, request: rpc::Call) -> Self::Out {
    match self {
      Web3Transports::Http((_, transport)) => Box::new(transport.send(id, request)),
      Web3Transports::Ws((_, transport)) => Box::new(transport.send(id, request))
    }
  }
}
That's good news, means I'm very close
Thank you so much!
Razi Marjani
@srmarjani
Apr 22 11:18
Hi all!! I am a new rustacean and I am so excited to be here
David Harvey-Macaulay
@alteous
Apr 22 11:46
Hi, why might I be seeing this error message? I have many other #[derive(Validate)]s working OK in the rest of the crate:
     Running `rustc --edition=2018 --crate-name gltf_json gltf-json/src/lib.rs --color always --crate-type lib --emit=dep-info,link -C debuginfo=2 --cfg 'feature="default"' --cfg 'feature="names"' -C metadata=e7ea350b701e9e11 -C extra-filename=-e7ea350b701e9e11 --out-dir /home/alteous/gltf/target/debug/deps -C incremental=/home/alteous/gltf/target/debug/incremental -L dependency=/home/alteous/gltf/target/debug/deps --extern gltf_derive=/home/alteous/gltf/target/debug/deps/libgltf_derive-b52f8a98a8ddb21e.so --extern serde=/home/alteous/gltf/target/debug/deps/libserde-5032916c97e66b6f.rlib --extern serde_derive=/home/alteous/gltf/target/debug/deps/libserde_derive-c27f30033ca5d502.so --extern serde_json=/home/alteous/gltf/target/debug/deps/libserde_json-c355870604138ced.rlib`
error: cannot find derive macro `Validate` in this scope
  --> gltf-json/src/camera.rs:59:48
   |
59 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)]
   |
Ah, I figured it out: I needed to use gltf_derive::Validate.
Pascal Precht
@PascalPrecht
Apr 22 12:30

@alteous you still around?

I think I need to bother you once more. I'm super close but I'm getting a compiler error that doesn't make any sense to me...

Given:

impl Transport for Web3Transports {
  type Out = Box<dyn Future<Item = serde_json::Value, Error = web3::Error>>;
  fn prepare(&self, method: &str, params: Vec<serde_json::Value>) -> (RequestId, rpc::Call) {
    match self {
      Web3Transports::Http(transport) => transport.prepare(&method, params),
      Web3Transports::Ws(transport) => transport.prepare(&method, params)
    }
  }

  fn send(&self, id: RequestId, request: rpc::Call) -> Self::Out {
    match self {
      Web3Transports::Http(transport) => Box::new(transport.send(id, request)),
      Web3Transports::Ws(transport) => Box::new(transport.send(id, request))
    }
  }
}

Where rpc is:

use jsonrpc_core as rpc;

Exactly like Web3 is using it. Same thing, same version, I get this error:

error[E0053]: method `prepare` has an incompatible type for trait
  --> /Users/pascalprecht/projects/vibranium/src/blockchain/connector/mod.rs:64:3
   |
64 |   fn prepare(&self, method: &str, params: Vec<serde_json::Value>) -> (RequestId, rpc::Call) {                                                                               
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `jsonrpc_core::types::request::Call`, found enum `jsonrpc_core::Call`
   |
   = note: expected type `fn(&blockchain::connector::Web3Transports, &str, std::vec::Vec<jsonrpc_core::serde_json::Value>) -> (usize, jsonrpc_core::types::request::Call)`       
              found type `fn(&blockchain::connector::Web3Transports, &str, std::vec::Vec<jsonrpc_core::serde_json::Value>) -> (usize, jsonrpc_core::Call)`                       
note: Perhaps two different versions of crate `jsonrpc_core` are being used?
  --> /Users/pascalprecht/projects/vibranium/src/blockchain/connector/mod.rs:64:3
   |
64 |   fn prepare(&self, method: &str, params: Vec<serde_json::Value>) -> (RequestId, rpc::Call) {

Getting those for both methods.

The same error appears, when I type out the exact same type (jsonrc_core::types::request::Call).

Any ideas what Rust is trying to tell me here?

I think I'm this close to getting this to work :D
Pascal Precht
@PascalPrecht
Apr 22 12:48
Is it maybe related to the fact that jsonrpc_core re-exports ::types from itself?
Denis Lisov
@tanriol
Apr 22 14:15
@PascalPrecht Note that web3 depends on jsonrpc_core version 8.something, so to be compatible you also need a version from the 8.x series.
Pascal Precht
@PascalPrecht
Apr 22 16:31
I checked the source on GH and saw that it uses 11.0.0
Will try going with 8.x
Denis Lisov
@tanriol
Apr 22 17:07
docs.rs entry in the crate popup has jsonrpc-core ^8.0.1. Another good way of checking dependencies is to install and use cargo-tree