Why can the type not be inferred here?

With this code:

use std::fmt::{self, Display, Formatter};

pub enum MarkupDisplay<'a, T> where T: 'a + Display {
    Safe(&'a T),
    Unsafe(&'a T),
}

impl<'a, T> MarkupDisplay<'a, T> where T: 'a + Display {
    pub fn mark_safe(&mut self) {
        *self = match *self {
            MarkupDisplay::Unsafe(t) => MarkupDisplay::Safe(t),
            _ => { return; },
        }
    }
}

impl<'a, T> From<&'a T> for MarkupDisplay<'a, T> where T: 'a + Display {
    fn from(t: &'a T) -> MarkupDisplay<'a, T> {
        MarkupDisplay::Unsafe(t)
    }
}

pub fn safe<'a, D, I>(v: &I) -> MarkupDisplay<'a, D>
where
    D: fmt::Display,
    I: Into<MarkupDisplay<'a, D>>
{
    let mut res = v.into();
    res.mark_safe();
    res
}

I get an error:

error: the type of this value must be known in this context
  --> askama_shared/src/filters/mod.rs:28:5
   |
28 |     res.mark_safe();
   |     ^^^^^^^^^^^^^^

First of all, it seems like the type of res should be extremely clear given the input types... Second of all, why is the compiler only complaining on the second line of the safe() function, rather than on the first line?

While I'm no magician with the type-system, it seems you are running into the limitations of Rust's type inference. My guess is that Rust cannot conclusively select which Into implementation to pick (there may be some defaults available, I'm not sure..)

What should help is to help the type-checker with type hints, by using the "turbofish" operator: "::<>"
The turbofish allows you to specify which into implementation you wish to call, like so:
.into::<MarkupDisplay<_>>()

(It's mostly known from someIter.collect::<desiredType>()", and only few people know it by it's pet "turbofish" name.)

There seem to be some issue with the Into trait which however I do not understand fully. Changing one line to:

let mut res:MarkupDisplay<'a, D>  = v.into();

Gives back the following:

error[E0277]: the trait bound `MarkupDisplay<'_, D>: std::convert::From<&I>` is not satisfied
  --> src/main.rs:28:43
   |
28 |     let mut res: MarkupDisplay<'a, D> = v.into();
   |                                           ^^^^ the trait `std::convert::From<&I>` is not implemented for `MarkupDisplay<'_, D>`
   |
   = help: consider adding a `where MarkupDisplay<'_, D>: std::convert::From<&I>` bound
   = note: required because of the requirements on the impl of `std::convert::Into<MarkupDisplay<'_, D>>` for `&I`

error: aborting due to previous error(s)
1 Like

I managed to at least fix the problem, though I still don't fully understand it. I changed safe to this:

pub fn safe<'a, D, I>(v: &'a I) -> Result<MarkupDisplay<'a, D>>
where
    D: fmt::Display,
    MarkupDisplay<'a, D>: From<&'a I>
{
    let mut res: MarkupDisplay<'a, D> = v.into();
    res.mark_safe();
    Ok(res)
}

From this:

pub fn safe<'a, D, I>(v: &I) -> Result<MarkupDisplay<'a, D>>
where
    D: fmt::Display,
    I: Into<MarkupDisplay<'a, D>>
{
    let mut res = v.into();
    res.mark_safe();
    res
}

So, in this case I guess the From <-> Into symmetry is not working, possibly because the Into would not be limited to types that live in this particular crate. Changing the trait bound to use From explicitly helped with that. I then had to annotate the &I argument with the proper 'a lifetime to make the lifetimes match.

Thanks for your help!

You can actually compile the original code with only one change - remove & from the signature of fn safe.
You require availability of conversion from I into MarkupDisplay<'a, D>> but then try to convert &I, not I into it.

2 Likes

Yup. It's important to remember that T: Trait doesn't imply that &T: Trait because T, &T, and &mut T are 3 different types (ie a reference isn't some modifier of a type, it's its own type).

1 Like

Yes vitalyd is correct so in fact this works correctly and is probably what you intended initially:

pub fn safe<'a, D, I>(v: &'a I) -> MarkupDisplay<'a, D>
where
    D: fmt::Display,
    &'a I: Into<MarkupDisplay<'a, D>>
{
    let mut res = v.into();
    res.mark_safe();
    res
}
1 Like

Or just keep what @mpol suggested but then pass references to it (as long as a Into exists for the reference). Then you let the caller decide whether they want the function to consume the value or not.

1 Like