Four years with Rust

December 21, 2016

Today is four years since I first learned about the existence of Rust. I know this because Rust 0.5 was the first release I used. Rust has changed a lot in that time. For a brief overview of its history, watch this talk of mine. But for today, I thought it would be fun to check out the release announcement and see what’s the same, and what’s changed.

rust-dev

First up, the announcement itself. The rust-dev mailing list used to be your one-stop shop for talking about Rust in a persistent form. But in January of 2015, we decided to close it down. Why? Let’s see what Brian said at the time:

You likely have already noticed, but traffic to rust-dev has decreased dramatically in recent months. This was a result of natural changes in project coordination at first, and then an intentional effort to phase out the list.In the beginning of the project there were only two places to talk about rust: #rust and rust-dev, and they served us well for all purposes for several years. As Rust grew though the coordination of the project moved to different venues, conversations were happening in a number of other places, and the purpose of rust-dev became less clear. At the same time there were also a number of heated and essentially unmoderatable discussions that caused many to lose faith in the effectiveness of the mailing list.

I love mailing lists, but I know I’m fairly unique in that. But they do have one big problem, which is that the tools for moderating them are extremely thin. You can pretty much ban someone, and that’s about it. No removing only certain posts, no cooldown periods, no shadow banning. While Rust has a generally positive reputation for community and moderation, that doesn’t mean things were always perfect. We learned some lessons the hard way.

More than that, mailman represents something of the “old guard” of open source. People see it as a sign of something backwards. Very few people want even more email. The culture is changing. We recognized this, and decided to move to Discourse instead. rust-dev was survived by two children, users and internals. This created a clear split between “discussion about Rust” and “discussions about building Rust.” It’s still free/open source software, it has good moderation tools, and you can even have it send you email!

We also don’t make release announcements on these forums; we do it on our blog.

Let’s dig into those notes!

900 changes, numerous bugfixes

900 changes is a lot! At the time, Rust was on a roughly six-month release schedule, so that’s about five patches merged per day. Rust 1.14 will have 1230 patches over its six-week release cycle: that’s just over 29 per day. That’s a much, much higher velocity. And that only counts the main repository; we’ve since added many more, like Cargo and crates.io. This is actually something I’ve been worried about for a while now; we only credit people for commits to rust-lang/rust in release announcements, basically due to this history, but there’s a lot more work that goes on here these days. I want Rails Contributors, but for the Rust project. Anyone want to work on it with me sometime?

Syntax changes

Rust went through several iterations of “when do you move, when do you copy” over its lifetime. Today, that distinction is made between types that implement Copy and ones that don’t. But older Rusts had various schemes. Given that this one was removed in the first version of Rust I used, I don’t exactly remember this, but I think it was

x <- y; // move
x = y; // copy

Rust also had this at one point in time, I believe:

x = move y; // move
x = y; // copy

We decided to unify the two syntactically for two reasons: one, the annotations felt like busywork, the compiler would tell you what to put if you got it wrong. Secondly, at least in today’s Rust, move and copy are identical operations, with the exception of what you can do with the older variable after the move. Sharing the syntax reinforces that.

Niko’s blog has a lot of great history in it, and this topic is no exception.

Long ago, Rust had a “no keywords can have more than five letters” rule, and so many things were abbreviated. This particular extension lives on today, but with the longer name format!.

Rust’s vector and array types, as we know them today, went through a lot of internal iteration, across many axes. The moral equivalent of [T]/N today is [T; N], but I’m sure there are/were lots of subtle differences between the two. I can’t quite remember.

These live on in some sense; they’re still not stable, so much so that they’re note even shown in the official docs. Manish hosts a copy though. These are tools for “syntax extensions”, Rust’s most powerful form of metaprogramming. A final design for them was just accepted eight days ago, so it’ll be a while still before you see this on stable Rust.

I don’t know why macros couldn’t do this before, but they still can today, and it’s very useful.

This is a fun edge case. Here’s the problem:

struct Env<F: Fn(i32)> {    f: F,}let e = Env { f: |i| println!("Hello, {}", i) };e.f(); // what does this do?

According to this entry, this would be a method call. In today’s Rust, this would be an error, you need to write (e.f)(); At least the error tells you exactly what to do!

error: no method named `f` found for type `Env<[closure@<anon>:6:22: 6:50]>` in the current scope
 --> <anon>:8:7
  |
8 |     e.f(); // what does this do?
  |       ^
  |
note: use `(e.f)(...)` if you meant to call the function stored in the `f` field
 --> <anon>:8:7
  |
8 |     e.f(); // what does this do?
  |       ^

We now have a more generic way to derive traits: #[derive(Eq)], for example. Eq is still here today, but IterBytes is not. I can’t recall exactly what it did.

Today, the list of traits you can derive is still only blessed ones by the compiler, and it’s a huge cause of people using nightly today, for better ergonomics with libraries like Serde and Diesel. But as of Rust 1.15, this restriction will be lifted, and one of the largest blockers of people using stable Rust will be eliminated! ???

The moral equivalent of .rc files today are .crate files, which Cargo will upload to crates.io. But unlike in the 0.5 days, you almost never need to worry or think about them, since Cargo makes it Just Work (tm).

This is true today, and is a little known fact! It works like this:

struct Point {    x: i32,    y: i32,}fn takes_point(Point {x, y}: Point) {    println!("({}, {})", x, y);}fn main() {    let origin = Point { x: 0, y: 0 };    takes_point(origin);}

That is, arguments to functions are PATTERN: TYPE, not NAME: TYPE. Inside of takes_point, x and y are in scope, rather than a whole point.

Semantic changes

Rust did have objects, long ago, but I thought they were removed at this point. This one is unclear to me.

Worth mentioning that ~ is Box<T> now, though. That’s a long story…

These are still in Rust today.

This is true today:

enum Foo {
    Variant { x: i32, y: i32 },
}

This is still true!

I think the “nominal” here is alluding to something older in Rust’s type system; we don’t make these kinds of distinctions today, as IIRC, all types are nominal. Not 100% sure.

This is true today.

I alluded to this above with <-.

*T is *const T or *mut T today, but this is still true:

let x = &5;
let y: *const i32 = x;

We’ve gone through many different iterations of “what coerces and what doesn’t” over the years; I actually expected the example with *const i32 above to require an as.

This is true today. I really like it, but a lot of people find it confusing. Basically, “use” always starts from the root of the crate. So

mod bar {    struct Foo;    mod baz {        use bar::Foo; // from the root        use super::Foo; // if you want to start from the parent        use self::super::Foo; // self makes it be relative. Not even sure if self + super works, but you get the idea.    }}

Not sure on this one, it’s too in the weeds.

Improved support for language features

This has always been a bit of a misnomer, inheritance means:

trait Foo: Bar {
}

If you implement Foo, you must also implement Bar. That’s it. This line makes me smile: “it works more often.”

Today’s Rust doesn’t just support this; it’s required! However:

@self was supposed to be a garbage-collected type, but was never really more than refcounting.

In addition, the last two don’t actually work like that: they would be self: Rc<Self> and self: Box<self>. The Box version will compile today, but the Rc version does not, because Box is magical. Eventually this will be rectified.

Another “yay more features work” line. :)

This works today, and is very useful. Consider Iterator: all of those methods have default implementations, so you only need to define next(), and you get the rest automatically.

Libraries

Conditions were a Lisp way of handling errors that Rust supported for a while. They were really cool, but nobody really knew how to effectively use them, so they never got used, so they’re gone now.

std::sort doesn’t exist anymore, though we do have a sort function on slices. We don’t declare it to have a particular algorithm, though we do say “This sort is stable and O(n log n) worst-case but allocates approximately 2 * n where n is the length of self.”

Recently it was improved a ton, by a first-time contributor, no less! Extremely well-done. In the PR, he explains the algorithm:

However, if we take the main ideas from TimSort (intelligent merging strategy of sorted runs) and drop galloping, then we’ll have great performance on random inputs and it won’t be bad on partially sorted inputs either.

TimSort is still around, kicking.

Today this is std::collections::binary_heap.

No idea where this went.

This is true today. See the above comments about Serde, though. rustc-serialize got to cheat though, and the compiler understands RustcEncodable and RustcDecodable. Can’t wait for Rust 1.15.

We’ve moved getopts out of tree, but it’s still there.