Rust Into Implementation

2 minute read

TLDR

To do type annotations during method call for Impl Into try Into::<dest_trait>::into(arg).

Scenario

I’ve to make HTTP request to some service and the request needs to serializable. To make actual request I’ll use http crate.

Let’s get on with it

I’ve a enum of HTTP methods, let’s say MyMethod. Why new enum when there’s already a struct Method in http crate; cause I need it to be serializable. Also the purpose is to tryout Into/From.

#[derive(Debug, Serialize, Deserialize)]
enum MyMethod {  
    GET,  
    POST,  
}

I’ll use the enum to construct http::Request -

let method = MyMethod::GET;
let request_fut = Request::builder()  
    .method(???)
    .uri(&some_url)  
    .body(())  
    .unwrap();

Now the method(...) doesn’t want MyMethod as a parameter, it needs http::Method or String, obviously! So I need to convert MyMethod in to http::Method. So my initial thought is I saw somewhere some code were using .into()! That was cool, I’ll do the same!

impl Into

https://doc.rust-lang.org/std/convert/trait.Into.html

impl Into<http::Method> for MyMethod {
    fn into(self) -> http::Method {
        match self {
            MyMethod::GET => http::Method::GET,
            MyMethod::POST => http::Method::POST
        }
    }
}

That’s all, fairly simple, right! Now let’s build the request -

let method = MyMethod::GET;
let request_fut = Request::builder()
    .method(method.into())
    .uri(&some_url)
    .body(())
    .unwrap();

And run cargo check. Why not build/run? Because build will take lot more time than cargo check and will check for all error. But in rust, if program compiles, it works.

error [E0284]: type annotations needed: cannot resolve `<http::method::Method as std::convert::TryFrom<_>>::Error == _`
  --> src/main.rs:89:14
   |
89 |             .method(method.into())
   |              ^^^^^^

error: aborting due to previous error

Uh-oh!! It seems compiler can’t resolve the type, maybe specifying the type will work. But I’ve no idea how to specify type during function call :(. So I’ll go with the way I already know first, by defining new variable, just to test my assumption.

let method = MyMethod::GET;
let http_method:http::Method = method.into();
let request_fut = Request::builder()
    .method(http_method)
    .uri(&some_url)
    .body(())
    .unwrap();

Yay! It works!! But some cosmetic issue remains; I don’t want to define a new variable every time I call into()! I want to specify type annotation during method call. After some googling - it seems type annotation can be done as Into::<expected_generic_type>::into(obj)

let method = MyMethod::GET;
let request_fut = Request::builder()
    .method(Into::<http::Method>::into(method))
    .uri(&some_url)
    .body(())
    .unwrap();

Unnecessary notes

It was harder than my expectation to find the above solution; maybe because of my limited understanding of rust. At first I came up with slightly alternative syntax -

let method = MyMethod::GET;
let request_fut = Request::builder()
    .method(<MyMethod as Into<http::Method>>::into(method))
    .uri(&some_url)
    .body(())
    .unwrap();

This syntax is maybe more generic and specially useful to resolve trait ambiguity during function call. See here for details.