Strange opaque type inference rules

Hi everyone!
Im have strage error in code below, i want understand why it happens and how to fix it, but mostly why its happen.

pub(super) fn create_basic_success_embed(
    description: impl Into<String>,
    title: Option<impl Into<String>>,
) -> CreateEmbed {
    CreateEmbed::new()
        .color((0x33, 0xEE, 0x22))
        .title(title.unwrap_or("title"))
        .description(description)
}

Error:

mismatched types [E0308] 
expected type parameter `impl Into<String>`, found `&str` 
Note: expected type parameter `impl Into<String>` 
found reference `&'static str` 
Help: the return type of this call is `&'static str` due to the type of the argument passed 
Note: method defined here

I dont understand why compiler can not infer opaque type here title.unwrap_or("title"). This method has signature in this case .unwrap_or(&self, impl Into<String>) but this method .description(description) has signature .description(&self, impl Into<String>) also, and if i write like this .description("here &str also") inference have no problems.

That’s an incomplete error message and the kind of reduced (and hardly comprehensible without context) thing you get from copying stuff out of IDE tooltips. If you don’t know how to make your editor display a complete error message, feel free to fetch it from the terminal invocation of cargo check

3 Likes

I assume you are trying to pass a string slice (&str) as your title, even though your function expects title to be an Option<_>. Try wrapping your argument in a Some, like Some(title.unwrap_or("title")) when calling create_basic_success_embed. Example.

Here compiler error:

error[E0308]: mismatched types
   --> dota-adapter-driver-discord\src\commands\embeds.rs:9:32
    |
5   |     title: Option<impl Into<String>>,
    |                   ----------------- expected this type parameter
...
9   |         .title(title.unwrap_or("title"))
    |                      --------- ^^^^^^^ expected type parameter `impl Into<String>`, found `&str`
    |                      |
    |                      arguments to this method are incorrect
    |
    = note: expected type parameter `impl Into<String>`
                    found reference `&'static str`
help: the return type of this call is `&'static str` due to the type of the argument passed
   --> dota-adapter-driver-discord\src\commands\embeds.rs:9:16
    |
9   |         .title(title.unwrap_or("title"))
    |                ^^^^^^^^^^^^^^^^-------^
    |                                |
    |                                this argument influences the return type of `unwrap_or`
note: method defined here
   --> C:\Users\fredo\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\option.rs:990:12
    |
990 |     pub fn unwrap_or(self, default: T) -> T {
    |            ^^^^^^^^^
1 Like

Signature of title():

pub fn title(mut self, title: impl Into<String>) -> Self

I use Option<> here just for default value.

The impl Into<String> is a shorthand that introduces a type parameter implicitly.

fn create_basic_success_embed(
    description: impl Into<String>,
    title: Option<impl Into<String>>,
) -> CreateEmbed {
    CreateEmbed::new()
        .color((0x33, 0xEE, 0x22))
        .title(title.unwrap_or("title"))
        .description(description)
}

is a shorthand for, essentially:

fn create_basic_success_embed<T: Into<String>, U: Into<String>>(
    description: T,
    title: Option<U>,
) -> CreateEmbed {
    CreateEmbed::new()
        .color((0x33, 0xEE, 0x22))
        .title(title.unwrap_or("title"))
        .description(description)
}

in other words, for any pair of types that support Into<String>, there’s a specific version of create_basic_success_embed that works with those two types.

unwrap_or takes an Option<U> and a value of the same type U, and produces a resulting value of type U.

I don’t know where you get this .unwrap_or(&self, impl Into<String>) signature; perhaps your IDE hints might display this usage of unwrap_or as expecting “impl Into<String>”, or you deduced that yourself, however that’s very different from a function (like description) that directly declares impl Into<String> it its own signature. (By the way, it’s also not &self but an owned self argument for Option::unwrap_or!)

If you have a function that needs two values of the same type

fn g<T>(x: T, y: T) {}

then a caller that uses impl Trait doesn’t change or get around that. These impl Into<String> occurrences in the signatures aren’t even called “opaque type”s by the way, that term is usually used for “impl Trait” return types only (a language feature with an – perhaps unfortunately – significantly different meaning).

If you create fn f(x: impl Into<String>, y: impl Into<String>) { … }, then x and y can have different types. And thus, e.g. none of the following works:

fn f(x: impl Into<String>, y: impl Into<String>) {
    g(x, y); // doesn't work!
    g(x, "hi"); // doesn't work!
    g(String::new(), "32"); // doesn't work!
}

Option::unwrap_or is similar in that it has self: Option<T> and the other argument default: T must have the same type.


In your use-case, the issue can easily be solved. The usage of impl Into<String> on these methods is just a convenience feature anyways! (If you look at the source, you’ll see the first thing the method does is convert it to a String anyways.) So you can just produce a String yourself earlier in order to unify the title: impl Into<String> (which has a type that your functions caller chooses) and &str for the default value "title". Here’s how that could look like:

.title(title.into().unwrap_or_else(|| String::from("title"))

the usage of unwrap_or_else allows you to avoid the cost of creating the String from the string literal "title" in the cases it isn’t needed.

6 Likes

Thank you, very clear explanation

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.