UX feedback from a Rust newbie

I wanted to give some feedback about my experience taking the very first steps in Rust and two things that let me stumble while taking them.

So in the spirit of Becoming a Contributor by @chriskrycho, I wanted to share my personal "pain points" with you.


The first one is one for WG-compiler-errors.

Consider the following code:

/// A Point
///
/// ```
/// assert!(false);
/// ```
#[derive(Debug)]
pub struct Point {
    x: f64,
    y: f64,
}

/// The main function.
pub fn main() {
    let p = Point { x: 0.0, y: 0.0 };
    println!("A point: {}", p);
}

Trying to compile it will cause the following error:

error[E0277]: `Point` doesn't implement `std::fmt::Display`
  --> src/main.rs:15:29
   |
15 |     println!("A point: {}", p);
   |                             ^ `Point` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
   |
   = help: the trait `std::fmt::Display` is not implemented for `Point`
   = note: required by `std::fmt::Display::fmt`

The problem with the error message is the :? part.

If you have zero experience with Rust then you simply don't understand that you should replace the {} with a {:?}. So my initial reaction to that message was "wat?" followed by a tentative println!("A point: {}", p:?);. One reason for my mistake was that the ^ points to p while the suggestion is actually talking about the {} further to the left.

I'd suggest to replace the "try using :? instead" part with "try using {:?} or {:#?} instead" and, ideally, also point to the correct placeholder.

This would serve two purposes:

  • it would clarify where the :? is supposed to be added.
  • it would mention {:#?} pretty-printing, an awesome feature that is currently not promoted enough.

While this whole thing isn't an issue for people that worked with Rust beyond the very first steps I'd argue that every newcomer will, at some point, be confronted with this error message.

I watched a coworker playing with Rust for the first time and he stumbled in a similar way at this very same error message. That's still just anecdotal evidence but tells me that it's not just me.


The second thing that caused me headaches is already hidden in the source code above and took my quite some time to solve...

When you start out with your first project beyond using the playground you are likely doing something like cargo new example (with an implicit --bin), cd example followed by cargo run, cargo test and cargo doc --open.

My code above contains an assert!(false); in the documentation of the Point type so I would expect a failing test... but that didn't happen.

First of all, it's not apparent that doctests aren't executed. It's a habit of mine to let tests fail on purpose at least once ("never trust a passing test") so I realized pretty fast that my doctests were not processed.

That didn't exactly help me, though, because I had no idea at all about why they were ignored.

My very first guess was that doctests probably aren't in stable yet... so I tried my code with beta and nightly.

Didn't help.

I read the testing chapters of "The Rust Programming Language" (second edition) and the tests chapter of "The Cargo Book" but wasn't any wiser.

Then I found "(note: this only works in library crates, not binary crates)" in the testing chapter of "The Rust Programming Language" (first edition) which finally pointed me into the right direction...

If I write doctests, I definitely expect them to be executed. Otherwise cargo doc generates documentation including supposedly working example code that hasn't actually been executed or even compiled. And that's not good at all.


Beside those two points I had a lot of fun. :slight_smile:

I hope this feedback is somewhat valuable since it touches on points that seasoned devs likely won't even see anymore. Because of course it has to be {:?} and, yep, doctests only work in library crates.

26 Likes

Thanks. The doctests in binaries was a surprise to me too! I've filed a bug about it: https://github.com/rust-lang/cargo/issues/5477

3 Likes

https://github.com/rust-lang/rust/pull/50441

3 Likes

Oh wow, I've been using Rust for a couple years and never knew that!

I typically put all my application's functionality and documentation in a library, making the binary 50-100 lines of code for setting things up, parsing CLI arguments, then feeding it to the library code. From what I've seen this design is fairly common in the community, so that probably explains why such a glaring bug was never noticed/fixed.

Wow! That was fast! I'm seriously impressed, thanks a lot!

It makes sense to work like this and is certainly a good best-practice.

The thing is that I really started from zero, i.e. I was parsing some string in main and fanned out from there. It was always my plan to finally end up with my very first crate.
In the process I split code from main into my very first own function and wanted to write my very first doctest which did... nothing.

I naturally suspected that I did something wrong, so I gave up and wrote normal tests instead to get on with what I wanted to actually implement. This was before I even knew about mod and how to use it.

Fast-forward two days and I had a tested mod but was still unable to write any doctests.

At that point I was getting quite frustrated and was convinced that I must be missing something super obvious because everyone was using doctests just fine in lots of tutorials I watched in the meantime. That's when I stumbled over the tidbit above in the first edition, more or less by accident. It turned out that I just had to create a lib.rs file and pub use the stuff that contained the doctests.

Given that Cargo switched the new default from --lib to --bin recently and older tutorials/talks often just use cargo new projectname, I suspect that more people will have similar issues in the future.

There's also another related problem:

/// ```
/// assert!(false);
/// ```
fn foo() -> u64 {
    // [...]
}

The above doctest also won't execute. I suspect I understand why it is ignored (rustdoc probably isn't interested in non-pub code at all) but that doesn't make the behavior more intuitive either.

My problem with this situation is that the code appears to be a doctest but actually isn't. It doesn't even have to be syntactically correct because it is ignored entirely. If you just take a glance at the code, chances are that you don't realize the absence of pub and take the "doctest code" for granted. Beside that, you could make a build fail by simply adding a pub cause all of a sudden, the broken "doctest code" would be considered real doctest code.

That isn't ideal and - in my opinion - violates the principle of least astonishment.

4 Likes

Have to agree on that it's definitely underpromoted. Basically I only learned about it by accident when reading the fmt module doc while looking for something else...

Just because GCC has atrociously user-unfriendly behavior doesn’t mean that Rust should slack off. I’ve encountered a lot of prejudice and skepticism of Rust from career C++ programmers, possibly because to them it’s just another in a long line of “C++ replacements” (Java, .NET, Go, etc) and about half the people I talk to haven’t even heard of it. Everything that can be done to make it easier to adopt is a competitive advantage, and at the very least it makes people’s lives easier.

Most people don’t bother to go onto a mailing list or engage in two-way communication that they expect to be slow or unhelpful, they just develop a negative impression of the thing and give up. For every person that comes here with an explicit problem, it’s not unlikely that there were several other people for whom that same problem became the straw that broke the camel’s back because they didn’t have the time to deal with it and gave up on Rust. Ending up with the attitude that “it seems like it has some cool ideas, but there are too many rough edges in practice”.

I think the formatting error problem could also be solved/improved by making the error smarter and telling people exactly what needs to change and what it needs to get changed to:

default formatter ‘{}’; try using {:?}

That eliminates a round-trip to the docs to move past the compiler error if people don’t understand where to put ‘;?’. The tricky part is that they might have other formatting directives in there - ideally it would show those as well in both the current and example literals, but it could also fall back to the original behavior if it isn’t just {}. The most frequent first-time case is probably where somebody is only trying to use with “{}”.

EDIT: Also, there is a discoverability problem with {:#?}. And yes, you can say rtfm, but going through the entire book requires a concerted determined effort that somebody may not be convinced of if they aren’t already familiar with the language. And they may simply not retain without not being reminded of it.

Sorry for the rant.

2 Likes

It took me two years of writing rust before I found the std::fmt documentation and learned about all the syntax. That was basically the last place I ever expected to find it.

2 Likes

Yes that's definitely true. It's nice for beginners to have a more helpful compiler than GCC. But in the long run I don't think it adds a lot of value. You are not going to convince people who use C++ to switch to your programming language because the compiler tells you how to use formatters, especially when your compiler is as verbose as the rust compiler which prints tons of garbage messages when you don't use imports etc

The mailing list is a last resort option and not a place where you are supposed to ask stupid things. For instance the glibc documentation is over 1000 pages long. If you can't find any helpful information in there or in the header/source files (which implies that you have a very specific issue) you can start spamming the mailing list.

Rust is not like Haskell where the documentation says: r,p=> f map x y foldr u s o (sarcasm off), Rust is a well documented language, so please use the documentation...It's meant to be read

This is getting a bit heated. Please chill out a bit.

I'm not sure exactly which list you're talking about, but to be clear: questions of any kind are welcome on this list/forum, and there are no stupid questions as far as we're concerned.

14 Likes

Sorry for the earlier rant

You are not going to convince people who use C++ to switch to your programming language because the compiler tells you how to use formatters, especially when your compiler is as verbose as the rust compiler which prints tons of garbage messages when you don’t use imports etc

On its own, yes, I agree, a single message is not going to deter someone. However the Rust compiler is going to provide more errors than most other languages. If the errors are legit and the compiler is telling someone exactly what they need to fix it, they’ll probably walk away with a positive impression.

If they have to keep going back to the docs, they’ll probably start to get frustrated from the context switching. Somebody just starting out is not going to realize how much testing and debugging time that strictness is going to save them down the line. They’ll just view it as a lot of rough edges.

Somebody coming from a python background will be very comfortable with Rust’s formatting - somebody from a pure C++ background will have to learn it. So it could be another new thing they’ll have to come up to speed on to a certain amount, whereas improving the compiler error would permit them to elide that topic altogether as they’re just getting started, until they really need to worry about formatted output. Depending on what they’re doing with Rust, the only reason they may need to know formatting may be for debugging and error handling and {:?} may be the only thing they need to be guided to in order to keep moving.

2 Likes

In the past people have switched from GCC to Clang because of nice error messages (until GCC upped its game), and horror stories about STL errors have been keeping C programmers out of C++.

Anyway, this one improvement is now in the nightly!

--> src/main.rs:4:20
|
4 | println!("{}", vec!);
| ^^^^^^ std::vec::Vec<_> cannot be formatted with the default formatter
|
= help: the trait std::fmt::Display is not implemented for std::vec::Vec<_>
= note: in format strings you may be able to use {:?} (or {:#?} for pretty-print) instead
= note: required by std::fmt::Display::fmt

7 Likes

I've observed that people only say things like this when they know they're behaving poorly, skirting boundaries, and ruffling feathers. It always indicates to me an attitude of, "I'm not interested in being a part of this community, so it doesn't matter if I follow the rules or not; if I get booted, so what."

Just so you know how it comes across to some of us.

9 Likes