Why String not implement Display?

I tried to compile such code:

use std::{convert::TryFrom, fmt};

#[derive(Debug)]
enum Foo {
    A,
    B,
}
impl TryFrom<&[u8]> for Foo {
    type Error = String;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        todo!()
    }
}

fn convert_byte<T>(byte: u8) -> Result<T, String>
where
    T: for<'a> TryFrom<&'a [u8]>,
    for<'a> <T as TryFrom<&'a [u8]>>::Error: fmt::Display,
{
    let arr = [byte];
    T::try_from(&arr).map_err(|err| err.to_string())
}

fn main() {
    let foo: Foo = convert_byte(b'A').unwrap();
    println!("Results: {foo:?}");
}

and got error:

error[E0277]: `<_ as TryFrom<&'a [u8]>>::Error` doesn't implement `std::fmt::Display`
  --> src/main.rs:35:20
   |
35 |     let foo: Foo = convert_byte(b'A').unwrap();
   |                    ^^^^^^^^^^^^ `<_ as TryFrom<&'a [u8]>>::Error` cannot be formatted with the default formatter
   |
   = help: the trait `for<'a> std::fmt::Display` is not implemented for `<_ as TryFrom<&'a [u8]>>::Error`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
note: required by a bound in `convert_byte`
  --> src/main.rs:28:46
   |
25 | fn convert_byte<T>(byte: u8) -> Result<T, String>
   |    ------------ required by a bound in this function
...
28 |     for<'a> <T as TryFrom<&'a [u8]>>::Error: fmt::Display,
   |                                              ^^^^^^^^^^^^ required by this bound in `convert_byte`

but <_ as TryFrom<&'a [u8]>>::Error is String, and it obviously implement fmt::Display,
so what is the problem?

1 Like

Something about the HRTB is causing problems. If you specify a single type in the bound on T and bound that type with fmt::Display it works

Playground

use std::{convert::TryFrom, fmt};

#[derive(Debug)]
enum Foo {
    A,
    B,
}
impl<'a> TryFrom<&'a [u8]> for Foo {
    type Error = String;

    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
        todo!()
    }
}

fn convert_byte<T, E>(byte: u8) -> Result<T, String>
where
    T: for<'a> TryFrom<&'a [u8], Error = E>,
    E: fmt::Display,
{
    let arr = [byte];
    T::try_from(&arr).map_err(|err| err.to_string())
}

fn main() {
    let foo: Foo = convert_byte(b'A').unwrap();
    println!("Results: {foo:?}");
}

Since E is a single type parameter outside the HRTB, it can't vary with the HRTB lifetime which avoids all sorts of weird edge cases in the type system and diagnostics. It's obviously also not identical to your original definition so it's only a workaround, not a real fix.

3 Likes

Shortcoming of the current trait bound solver. If not exactly this one, there's a few related issues. (Search for "higher ranked e0277".)

4 Likes

The trait solver seems to have a better time with TryInto, with or without single associated type.

1 Like

Interesting, maybe because that version is constraining to a single TryInto output for all of the HRTB lifetimes?

Hmm...

for<'a> <T as TryFrom<&'a [u8]>>::Error: fmt::Display,
for<'a> <&'a [u8] as TryInto<T>>::Error: fmt::Display,

It goes about solving these different ways clearly, and after playing around a bit I think it's because it knows for sure who the implementer is in the second case (modulo lifetimes), &'_ [u8]. Where as for whatever reason, despite the type ascription, it has decided it doesn't know for sure what T is in the original example, and having T along with the HRTB makes it give up I guess.

To see what made me think this, read the error output carefully:

error[E0277]: `<_ as TryFrom<&'a [u8]>>::Error` doesn't implement `std::fmt::Display`
  --> src/main.rs:26:20
   |
26 |     let foo: Foo = convert_byte(b'A').unwrap();
   |                    ^^^^^^^^^^^^ `<_ as TryFrom<&'a [u8]>>::Error` cannot be formatted with the default formatter
   |
   = help: the trait `for<'a> std::fmt::Display` is not implemented for `<_ as TryFrom<&'a [u8]>>::Error`

See all those <_ as TryFrom<&'a [u8]>>'s? It doesn't know it's looking for Foo as TryFrom.

And indeed, after noticing that, I found that a change at the call site also avoids the error:

-    let foo: Foo = convert_byte(b'A').unwrap();
+    let foo = convert_byte::<Foo>(b'A').unwrap();

(But I recommend sticking with TryInto as in addition to apparently better inference properties, it's technically more accepting.)

2 Likes

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.