Making rust easy to learn and use

Semicolons make the implementation of the programming language easier and does not add a significant amount of cognitive overhead. There are plenty of semicolon related discussions on this forum, here is one: Why semicolons?

I have been programming since 1997 and I am currently programming Swift, TypeScript, Java, JSX/HTML, CSS, and Rust. Moving between them is seamless and I rarely forget where a semicolon is needed.

Swift is one of the worst "modern" programming languages (in my opinion) since it often gets confused / times out when doing type inference. JavaScript similarly does not require semicolons but can easily "miscompile" if the programmer isn't very careful.

Long story short: semicolons are doing real heavy lifting.

5 Likes

I don't think the difficulty of Rust is anything to do with syntax, it's that it has new paradigms which have to be learned. Especially the way references and Cells work, but other language areas as well ( match, the enum type and a lot else ).

8 Likes

One thing that may help is improving on the syntax footguns. I spent a few weeks fighting the borrow checker early on, in part because the syntax for references looks so much like pointer/objects in mainstream languages, and it is also covered too early in the Rust Book.

Explicit lifetime annotations should be in some advanced book or a closing chapter, and ideally with a verbose syntax that hints you away, to get through the oft heard point in this forum that if you use explicit lifetimes on your structs as a beginner, the design is probably wrong (for Rust).

There are other footguns (like the "as" operator, what a train wreck).

How are any of those "footguns"?

If you accidentally misuse them it's most likely the compiler will complain. Thus saving your foot.

Admittedly there can be confusion about the meaning of syntax and syntactic symbols when coming from other languages. A & in Rust is not exactly a & in C. But hey, it's a different language of course they can be different, we just have to get used to it.

I think references can be discussed early in learning the language. They are kind of essential if you want to use some object after you have had some function read or write it.

As for lifetimes, I don't know, perhaps I'm lazy. I try and get as much done without ever using a tick mark. Which turns out to be pretty much everything I have wanted to do so far.

4 Likes

The problem there is people blindly following compiler suggestions, not that the book teaches things about lifetime annotations.

Beginners will try to write

struct MyStruct {
    field: &str,
}

compiler says

   Compiling playground v0.0.1 (/playground)
error[E0106]: missing lifetime specifier
 --> src/lib.rs:2:12
  |
2 |     field: &str,
  |            ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 | struct MyStruct<'a> {
2 |     field: &'a str,
  |

For more information about this error, try `rustc --explain E0106`.
error: could not compile `playground` due to previous error
1 Like

Would it make sense for the compiler to explicitly suggest &'static str or String as another alternative in that case? If nothing else, the presence of two suggestions implies that a deliberate choice is needed by the programmer.

3 Likes

I would go as far as to say that also adding String as a suggested alternative is a must. Aaand now you’ve edited your post xD

:thinking: In the general case replacing T with &'static T or T can be useful; also Box<T>… sometimes Rc<T> or Arc<T> is also a good choice.

Edit: On a related note, it seems particularly bad that, currently, compiler suggestions will make you rewrite

struct MyStruct {
    field: &mut Vec<&str>,
}

into

struct MyStruct<'a> {
    field: &'a mut Vec<&'a str>,
}

resulting in two antipatterns: references in owned structs and re-using lifetime argument where you REALLY shouldn’t re-use them.

example sequence of suggestions (click to expand)
   Compiling playground v0.0.1 (/playground)
error[E0106]: missing lifetime specifier
 --> src/lib.rs:2:12
  |
2 |     field: &mut Vec<&str>,
  |            ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 | struct MyStruct<'a> {
2 |     field: &'a mut Vec<&str>,
  |

error[E0106]: missing lifetime specifier
 --> src/lib.rs:2:21
  |
2 |     field: &mut Vec<&str>,
  |                     ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 | struct MyStruct<'a> {
2 |     field: &mut Vec<&'a str>,
  |

For more information about this error, try `rustc --explain E0106`.
error: could not compile `playground` due to 2 previous errors
struct MyStruct<'a> {
    field: &'a mut Vec<&str>,
}
   Compiling playground v0.0.1 (/playground)
error[E0106]: missing lifetime specifier
 --> src/lib.rs:2:24
  |
2 |     field: &'a mut Vec<&str>,
  |                        ^ expected named lifetime parameter
  |
help: consider using the `'a` lifetime
  |
2 |     field: &'a mut Vec<&'a str>,
  |                        ^^^

For more information about this error, try `rustc --explain E0106`.
error: could not compile `playground` due to previous error
6 Likes

I thought the compiler did suggest using static in many cases. I have seen it and found myself going down totally the wrong road to get what I want done. Following that advice popped up another problem elsewhere, fixing that popped up yet another... I'm sure I have seen people arrive here after such a rabbit hole adventure.

Unless I have understood wrongly I almost never want anything static. Pretty much nothing I create is expected to have a lifetime as long as the entire program.

Perhaps suggesting String might be useful.

In the above example, String is definitely a more useful suggestion than &'a str (as long as the struct in question doesn't already have a lifetime parameter).

With lifetimes being a headline feature, it seems to me that Rust newcomers are already too-easily drawn to use them excessively, and they certainly don't need further encouragement from the compiler. (Speaking for myself, at least, it did take me a while to internalize the lesson that not everything needs a lifetime, and that boxing and cloning are often perfectly fine tools.)

Now, as a relative newcomer, let me state for the record that Rust is the most approachable language I've ever had the pleasure of learning, though I did have years of experience with unmanaged languages to draw on, and I imagine Rust will inevitably present a bit of a speedbump in that area to anyone who only has experience with managed languages (or no significant programming experience at all). Lifetimes or not, you just can't avoid thinking about ownership, something that most managed languages tend to gloss over. (I mean, in those languages, not thinking about ownership often leads to bad code, bad performance, or outright bugs, but the code does compile...)

As for 'static, I don't think the compiler suggests it unless there's an interaction with another 'static value (typically, a string literal)? As for its usefulness, it should not be dismissed out of hand. Obviously, all compile-time values are 'static, but 'static just means "from now and until the end of the program", which may not be a long life at all in (say) a command-line tool. Even if you're building something that'll continue running indefinitely, you'll often have data loaded early during execution (e.g. config files) that are used for the rest of the program lifetime, in which case Box::leak can be an incredibly convenient way of sharing a value far and wide with a lifetime that's always on hand, no template parameters needed.

4 Likes

If I remember correctly, the cases where I have seen people get lost after a suggestion to add 'static mostly relate to where there is a specific 'static bound (particularly with std::thread::spawn()). In those cases, it seems the Rust compiler suggests making any lifetimes 'static, when passing owned data is usually what is needed (Arc is usually a solution for the thread spawning case).

2 Likes

This suggestion seems particularly bad, as it will almost never be correct. One potential fix would be to never suggest reusing an existing lifetime parameter if that suggestion would change the lifetime's variance, but I don't know if the compiler has calculated the variances by the time this message gets generated.

9 Likes

Fair enough. However, it has to be noted that empirically, most suggestions to change Rust are proposed out of a sheer lack of understanding of the language or programming in general; there are quite a few especially drastic ones which obviously wouldn't fit into the language well; and that most of them aren't considered holistically, i.e. they are not serious at best.

Serious, well-fleshed out, worthwile RFCs mostly tend to come from people who already have a thorough understanding of the language; they are more often about enabling something big that is currently impossible (as compared to yet another piece of syntax sugar); and they are backed by a good amount of thought about interactions of a potential change with all parts of the language. "Let's get rid of semicolons because just because" and "why not do X since language Y also does X" are not such kind of proposals.

5 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.