
Experimenting with Rust generics and dynamic traits - dbeck74
http://dbeck.github.io/My-First-Steps-In-Rust/
======
finnyspade
22:34 error: the trait `core::fmt::Debug` is not implemented for the type `T`
[E0277]

22 println!("trait: {:?}", i);

This error message is fantastic! On line 22, i is of type T which does not
implement Debug. The hint is off, but the actual error is super useful.

Turns out if you google "Rust e0277" which is the error code he got, this is
the top result: [https://users.rust-lang.org/t/generics-or-how-do-i-solve-
an-...](https://users.rust-lang.org/t/generics-or-how-do-i-solve-
an-e0277-error/3072)

Which contains the same solution to his problem.

TL;DR Nothing to see here, just a case of "let me google that for you"

~~~
masklinn
edit: please don't downvote finnyspade into the negatives, that's not helpful

> The hint is off, but the actual error is super useful.

I completely disagree. The actual error is very basic and only useful if you
already know what it pertains to, that the hint be way off is actively
harmful.

> TL;DR Nothing to see here, just a case of "let me google that for you"

No, it's a case of the compiler not helping and just dumping the raw error in
your lap, let's not settle for being GCC when we can do better: [http://elm-
lang.org/blog/compilers-as-assistants](http://elm-lang.org/blog/compilers-as-
assistants)

You shouldn't need to google a bloody traits bound error (and may not even be
able to, because you're trying Rust on a bus during your commute and have run
out of data) (note that the rustc --explain output doesn't quite help either,
it also assumes you already understand trait bounds and also suggests
implementing the relevant trait on your type, which is the other way around
from the issue, a careful read of the code snippets may hint at the solution,
but the explanation doesn't point to it)

~~~
finnyspade
> The actual error is very basic and only useful if you already know what it
> pertains to

The error is really solid at showing a type mismatch. Which is of the form "I
wanted X but you gave me Y".

> that the hint be way off is actively harmful That's true and they could
> probably do a bit better for type mismatches on type variables vs concrete
> types

> just dumping the raw error What's the difference between a "raw error" and
> something else. If the hint was accurate would it still be a "raw error"
> because it's not styled like those elm errors? Any more info (aside from a
> better hint which I concede should be better) would have to be an actual
> explanation of static typechecking which seems a bit extreme.

All I'm saying is while this error isn't a paragon of what all good error
messages should be, it's no more cryptic than any other similar error. Even
the nicely formatted elm errors say essentially the same thing.

~~~
masklinn
> The error is really solid at showing a type mismatch. Which is of the form
> "I wanted X but you gave me Y".

That's not really high praises, "I expected Foo and you gave a Bar" is pretty
much level 0 of static type checking which even javac manages, you'd have to
work hard to fail to provide even that.

> What's the difference between a "raw error" and something else. If the hint
> was accurate would it still be a "raw error" because it's not styled like
> those elm errors?

No, that's the point, if you have extensive hints and decorations it's not
just the raw errors anymore.

In fact the Rust compiler/developers (to their credit) try not to provide raw
errors, they provides code pointers and hints and macro expansions, the issue
is for this error they're all unhelpful to downright incorrect.

> Any more info (aside from a better hint which I concede should be better)
> would have to be an actual explanation of static typechecking which seems a
> bit extreme.

That's not true, it could be an explanation of traits and trait bounds, and
that does sound helpful to Rust beginners (and not harmful in the least to old
hands).

> Even the nicely formatted elm errors say essentially the same thing.

The essentials are the hardly helpful[0] same in every compiler, Elm's error
messages are contextual not just in the decoration of the code but in that the
text uses more precise terms depending on the error's context, and they
provide better explanation and hints.

The compiler doesn't _have_ to whack your fingers with a ruler and yell
"WRONG".

[0] they're helpful if you have experience in the cryptic ways of the specific
compiler for the specific language, if that's our benchmark G++'s historical
error messages are helpful, that doesn't seem to be a majority view

~~~
arielb1
> That's not really high praises, "I expected Foo and you gave a Bar" is
> pretty much level 0 of static type checking which even javac manages, you'd
> have to work hard to fail to provide even that.

If you know the language well, there really isn't anything _more_ to say -
your code wants the `Debug` bound, but it is not present. Either change your
code or add the bound.

I am going to improve the error reporting to use a better error message in
this case.

~~~
masklinn
> If you know the language well

Right, that's the problematic assumption. The compiler message aren't going to
be problematic if the compiler already trained you.

~~~
steveklabnik
While I don't disagree with your point, I don't agree with your reason. It's
not that the compiler has trained you, it's understanding that you must bound
generics by a trait to be able to call methods on the resulting value. That's
a language-level understanding rather than a reading of tea leaves.

This is also one reason why --explain was added; hopefully the extended
diagnostics can help with unfamiliar errors. More experienced users need
--explain less.

~~~
masklinn
> it's understanding that you must bound generics by a trait to be able to
> call methods on the resulting value. That's a language-level understanding
> rather than a reading of tea leaves.

Sure but here's the thing: the compiler could know report that, as you note
that's language-level understanding, who's to better know the language than
the compiler?

> It's not that the compiler has trained you

Maybe saying that you[0] trained yourself (in this case to pattern-match that
a type mismatch between a generic T and a trait is pretty certainly a missing
bound on T) would be a better way to put it?

The point is, the compiler could[1] be much more precise in its diagnosis, and
much more assisting and "mentoring" in its output, but a few people argue that
it's unnecessary, because they've built not just a mental model of rust but a
library of pattern-matching "low-level" compiler errors into higher-level
language errors and can do it in their sleep at this point.

[0] generic, you, me, pcwalton, dons, oleg, basically anyone working with a
compiler

[1] I am very much _not_ saying it's easy

~~~
steveklabnik
I do certainly agree that we can do a lot more. There's always so many things
to do...

~~~
masklinn
Time to get this cloning thing up and running, might be easier to get more of
you than to get you alone to fix it all.

------
Fede_V
Cool blog. I tried playing around with Rust as well on some toy problems, and
had a similar experience: cryptic error messages that only made sense in
hindsight.

It's an awesome language, but I wish there was a bit more effort put into into
making it 'ergonomic'. Of course, it's an open source effort so nobody is
owned anything - but useful error messages are incredibly important,
especially when the language pushes so many new concepts.

~~~
benashford
How could that particular error (using that as an example) be improved?

> error: the trait `core::fmt::Debug` is not implemented for the type `T`

That pretty much is the problem. It seems the bigger pain point is as you say
"they make sense in hindsight", but that's because (as the author
acknowledges) of expectations driven by other programming languages. C++
wouldn't have raised an error because the type-checking is done after template
expansion, whereas Rust is the other way around.

The biggest criticism from me would be the hints underneath advising the
author to implement the Debug trait, whereas the solution is to add a trait
bound to the code instead.

If anything I'd say Rust's compilation errors are above average in both the
accuracy of the error message, and helpful hints. The learning curve around
concepts such as lifetimes and moved values and other rare features do take
time, however.

~~~
lhnz
I would like it to say something like "Either the trait `core::fmt::Debug` is
not implemented for the type `T`, or it is implemented but the compiler does
not know about it in this context."

Not sure if that is the language I would use, but that is what I want it to
say. Suggesting that I might be able to solve the problem by providing more
information to the compiler would help me realise how I could solve the
problem.

I've had similar problems when developing Rust code, and I wonder whether they
would be happy to accept changes to their error messages?

My biggest difficulties trying to understand Rust error messages were either
with lifetimes or errors emanating from within macros (it is very hard to see
what is failing when you have never seen the code itself.)

~~~
steveklabnik

      >  I wonder whether they would be happy to accept changes to their error messages?
    

We have a special tag just for this in the issue tracker, and care a lot about
diagnostics. Please report confusing errors if you see them, and patches are
even better.

People seem to be _really_ split about Rust's current errors: I've heard, in
the same day, "Rust has the worst error messages" and "Rust has the best error
messages". Trying to figure out how to get the former camp into the latter.

~~~
smt88
Is it possible to have error messages that work for everyone? Is there a
fundamental difference between what the former group want out of an error
message and what the latter group want?

~~~
steveklabnik
That's what I am trying to figure out. I have a hunch that it may be
correlated with language background.

That's usually why I'm always asking for specifics when people make general
complaints, I'm trying to dig into the _why_. "Error messages are bad" isn't
actionable; "error messages are bad because" often is. This thread was already
getting into specifics before I woke up, though :)

------
finnyspade
For interested parties, there's been an open issue on the hint text for this
error since September of last year. If ya want to help out take a crack at it!
[https://github.com/rust-lang/rust/issues/28660](https://github.com/rust-
lang/rust/issues/28660)

------
mrkgnao
Rust's traits seem similar to Haskell typeclasses. I saw the error at once,
perhaps for that reason: I could almost hear ghc whine about not being able to
deduce Show for arbitrary types.

~~~
bjz_
Rust's traits were indeed inspired by typeclasses, so those coming from
Haskell shouldn't have much difficulty with these kinds of errors. The tricky
thing is trying to make it easier for those who don't have that experience.

~~~
GaveUp
Hardly accurate but coming from the C# world I thought of traits as a hybrid
between an interface and extension methods. I think the biggest challenge was
that, at first glance, a trait does appear to be very close to an interface,
but really it is it's own beast.

------
Sharlin
The C++ working group has actually tried to provide a solution to this issue -
that templates are only type-checked at instantiation time - for almost ten
years. The original "Concepts" specification was overly ambitious and was
dropped from C++11 - and just this year it was deemed that even the newer
"Concepts Lite" proposal is not yet ready for C++17.

------
infinity0
This is pretty much how every language does generics except C++, so it's not
actually an issue with Rust.

~~~
mmebane
I've often thought of C++'s templates (when used in the "straightforward"
manner, anyway) as more of compile-time duck typing than a real generic type
system. Even moving from C++ to Java took a while to get used to.

~~~
scott_s
Moving to Java generics took me a while mostly because of type erasure. In
C++, I'm used to being able to _use_ the type in a generic class or function,
which kept biting me in Java.

~~~
masklinn
What do you mean by _use the type_? Query for type metadata at runtime?

~~~
scott_s
No, that's not what I meant, but yes, that is a limitation. (Just not one I
hit.) The big one is not being able to allocate objects of that type. For
example, in C++, I can say:

    
    
      template <class T>
      T* allocate() {
        return new T();
      }
    

That's not possible in Java because of type erasure. When you try something
like that, you get an error message like:

    
    
      Erasure.java:12: error: unexpected type
                  return new T();
                           ^
        required: class
        found:    type parameter T
        where T is a type-variable:
          T extends Object declared in class Foo
    

Again, this is because of type erasure: Foo<T> is really Foo<Object>, where
Java just does a cast for you to T in all of the right places. It does not
actually _know_ the type. For me, that lead to all sort of contortions because
a class Foo<T> could not allocate objects of type T internally. I needed to
create factory methods in other places which Foo would call. In the instance
I'm thinking of, those factory methods ended up living in derived classes.

C++ templates have its issues, but it allows parametric polymorphism. In Java,
I could not write such code, which was what I was used to. Because of type
erasure, I was forced more towards subtype polymorphism, even though I was
implementing generic classes.

~~~
masklinn
I'm not quite sure that's due to type erasure. What would happen to this code
if T were an interface? Meanwhile even though (AFAIK) GHC uses type erasure,
you can do something like that and create values "de novo" if the typeclass
supports it e.g. Alternative's `empty` or MonadPlus's `mzero`

~~~
mmebane
Even if you constrain the type to be something you know is a class, it won't
work. [1]

The typical workaround in Java is to either pass around .class instances, or
to do tricks with anonymous subclasses to create a type token. [2]

[1]: [https://ideone.com/th18eh](https://ideone.com/th18eh) [2]:
[http://docs.guava-
libraries.googlecode.com/git/javadoc/com/g...](http://docs.guava-
libraries.googlecode.com/git/javadoc/com/google/common/reflect/TypeToken.html)

~~~
masklinn
> Even if you constrain the type to be something you know is a class, it won't
> work. [1]

Oh yes, I'm not saying it's possible to do this in Java, AFAIK you're right
that it's not, I'm saying I'm not sure it's because of _type erasure_ , at
least type erasure on its own.

~~~
scott_s
I think it is. As far as Java is concerned, T is Object in that context. It
can't generate code for "allocate a T" because Foo only actually operates on
Objects, not Ts. That is, there will be one version of Foo, and that version
will generate code to interact with Objects, not Ts. It can't allocate a T
because the type of T is erased from Foo. That is type erasure.

------
bsaul
right when i was about to jump into the elixir / phoenix bandwagon, thinking
its type hints would be enough for me ( who like types to be as strong as
possible)...

damn... when is the erlang pure actor model ( as welle as OTP) going to be
implemented in another language ??

~~~
dbeck74
I am waiting for that too. Especially Rust + OTP would be a big thing for me.

