Experimenting with Rust generics and dynamic traits
dbeck.github.io
dbeck.github.io
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-...
Which contains the same solution to his problem.
TL;DR Nothing to see here, just a case of "let me google that for you"
> 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
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)
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.
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
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.
Right, that's the problematic assumption. The compiler message aren't going to be problematic if the compiler already trained you.
This is also one reason why --explain was added; hopefully the extended diagnostics can help with unfamiliar errors. More experienced users need --explain less.
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
The examples on that page are all extremely simple type errors in the first place, whereas this error message relates to a much more advanced feature (typeclasses/traits). I'd like to see what the ideal trait mismatch error would look like.
I do think that this error could be improved by suggesting #[derive(Debug)], don't get me wrong. But I don't know of any compiler for any language that goes out of its way to explain how to implement an interface on every interface-not-implemented error.
That's what it's already doing:
> src/x/mod.rs:22:33: 22:34 note: `T` cannot be formatted using `:?`; if it is defined in your crate, add `#[derive(Debug)]` or manually implement it
and it's wrong, the error is a missing trait bound not a missing interface implementation
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.
TBF might be simpler for Elm as it eschews much in the way of type abstractions.
> 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.
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.)
Not arguing or anything about what error messages are valid. I just found your perspective about the "compiler not knowing" interesting and felt like sharing this.
I still think the error message could be a lot better, as the word 'implementation' is confusing. What's happened here is that Buffer was being used in a for-loop as if it always contained printable types, when actually there was no assurance that this was the case.
Edit: Highly recommend reading the comments of the two people that responded to me, as they're both more correct and more helpful while I was trying to work things out as I wrote my comments.
requires that "i" implements Debug, but
src/x/mod.rs:15 impl<T> Info for Buffer<T> {
does not specify that T must be Debug
hint: you can bound T to Debug:
impl <T> Info for Buffer<T> where T: Debug {
this means the code will only work with types which implements Debug,
but lets you use Debug operations in your methods
Worse, the rest of the error message gives completely the wrong direction for this error case, it gives an advice for a concrete type T, not for a generic one.Not quite. Rust's generics are instantiated statically - the compiler generates code specific to the type parameters passed in. The compiler must handle cases where it could not find a conforming function implemented for that the type, otherwise no code can be generated. Without the use of traits to constrain types you could get around this by printing a stack trace of the instantiation, but that causes even worse error messages - ie. the ones you see coming from C++ compilers.
> 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.
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 :)
Which probably means there's a steep learning curve to them, but they are really useful and informative after that initial learning period is over. If so, tread carefully lest you ruin the latter by catering to much to the former problem.
Rustc error reporting was deliberately based on that of clang.
In particular, I think it is going to be impossible to have error messages for lifetime errors that are solvable without doing at least some background reading.
Google/stackoverflow of course help, but, as an expert, I think you may be a bit biased by your own experience as to how helpful the compiler errors actually are.
I genuinely have no idea how you'd even go around improving lifetime errors, if somebody manages to crack that one I'd probably be willing to set up a recurring donation to whatever they wish.
Unfortunately I haven't got experience with generics in languages other than C++, so thanks for pointing this out.
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.
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 [2]: http://docs.guava-libraries.googlecode.com/git/javadoc/com/g...
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.
damn... when is the erlang pure actor model ( as welle as OTP) going to be implemented in another language ??