Expected opaque type, found a different opaque type

Hi All,

I have two functions that return exactly the same type and doing the same thing (see below) - unless my eyes are too tired and I miss something :slight_smile:. Calling these functions with if/else produces the error above.

Here’s the code:

use super::errors::*;
use crate::state::AppState;
use std::sync::{Arc, RwLock};

use failure::{format_err, Error};
use futures::future::Future;
use hyper::{Body, Request, Response};
use log::{info, warn};

pub fn handle_management(
    req: Request<Body>,
    state: Arc<RwLock<AppState>>,
) -> impl Future<Item = Response<Body>, Error = Error> {
    if req.uri().path().starts_with("/api/v1/") {
        handle_api_v1_request(req, state)
    } else {
        handle_management_request(req, state)
    }
}

pub fn handle_api_v1_request(
    req: Request<Body>,
    _state: Arc<RwLock<AppState>>,
) -> impl Future<Item = Response<Body>, Error = Error> {
    info!("Received api request: {}", &req.uri().path());
    let error = format_err!("api not yet implemented");
    internal_server_error(error)
}

pub fn handle_management_request(
    req: Request<Body>,
    _state: Arc<RwLock<AppState>>,
) -> impl Future<Item = Response<Body>, Error = Error> {
    warn!("Received management request: {}", &req.uri().path());
    let error = format_err!("management not yet implemented");
    internal_server_error(error)
}

The error is:

error[E0308]: if and else have incompatible types
  --> src/web/management.rs:17:9
   |
14 | /     if req.uri().path().starts_with("/api/v1/") {
15 | |         handle_api_v1_request(req, state)
   | |         --------------------------------- expected because of this
16 | |     } else {
17 | |         handle_management_request(req, state)
   | |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected opaque type, found
 a different opaque type
18 | |     }
   | |_____- if and else have incompatible types
   |
   = note: expected type `impl futures::future::Future` (opaque type)
              found type `impl futures::future::Future` (opaque type)

error: aborting due to previous error

Any idea what’s going on?

Thanks.

handle_management has to return a single, specific type, but handle_api_v1_request and handle_management_request return different types.

impl Trait is not an abstraction, but only hiding of the actual type name in the source code (it doesn’t add flexibility, only removes it).

To return one of two different things you need Either or dyn Trait.

3 Likes

Thanks, but I don’t understand that. both handle_api_v1_request and handle_management_request return impl Future<Item = Response<Body>, Error = Error>, actually they both end up returning the result of the same function. How come it’s not the same?

If it’s not obvious, I’m a complete newbie so chances are I miss something :frowning_face:.

Thanks @kornel, but what I fail to understand is why are these functions return different thing?

As I mentioned in answering another answer, I’m a complete newbie so please be patient :slight_smile:

impl Trait isn’t a type - it’s just a way of hiding what the return type actually is.

// this returns an i32
fn foo() -> impl Debug {
    6i32
}

// this returns a &str
fn bar() -> impl Debug {
    "hello"
}

// what type does this return?
fn combined(use_foo: bool) -> impl Debug {
    if use_foo {
        foo()
    } else {
        bar()
    }
}

combined won’t compile because one branch of the if statement returns an i32 and the other returns a &str, but those are not the same type.

You can write the types out explicitly:

fn foo() -> i32 {
    6i32
}

// this returns a &str
fn bar() -> &'static str {
    "hello"
}

fn combined(use_foo: bool) -> ????? {
    if use_foo {
        foo()
    } else {
        bar()
    }
}
1 Like

Ok, so following your example I had something like this:

use std::fmt::Debug;

fn foo() -> impl Debug {
    6i32
}

fn function_that_calls_foo() -> impl Debug {
    foo()
}

fn another_function_that_calls_foo() -> impl Debug {
    foo()
}

// what type does this return?
fn combined(use_foo: bool) -> impl Debug {
    if use_foo {
        function_that_calls_foo()
    } else {
        another_function_that_calls_foo()
    }
}

which indeed fails to compile. So the compiler actually doesn’t follow the functions to see if they actually return the same thing if it’s two levels deep?

Thanks for you patience :slight_smile:

Btw, Following @kornel solution to use dyn Trait does solve the problem, I just don’t understand why the compiler doesn’t follow through the functions.

In any case, thanks @kornel and @sfackler for your time. I learned a few new things :slight_smile:

The type is implementation detail. impl Trait hides it to such an extent that you (should) never know what the type was. The complier then treat it so no two functions will return the same type so trying two will become an error. (There are RFCs to change this.) What it does allow is the developer of such functions to change the implementation without breaking any code depending on the function signature.

1 Like

That’s done intentionally. We want to allow the function to be allowed to change what type it returns without breaking external code.

1 Like

Wow,

Thanks @john and @sfackler for clearing it up for me.