“I just started writing some Rust”(twitter.com)
twitter.com
“I just started writing some Rust”
https://twitter.com/id_aa_carmack/status/1089286703817412608
48 comments
Rust is in many ways more powerful because of what it can't do than what it can. Good constraints are liberating; they raise the level of what you can trust about your environment.
Thanks for comparing C++ templates to Rust's macro system and not to Rust's generics. The template system that C++ has is actually closer to Rust macros than it is to Rust generics, and I think a common misconception. This is e.g. something that people who complain that C++ has const generics while Rust doesn't should keep in mind.
I love C++ (though it often frustrates me), and I think Rust is doing some awesome things (though I don’t work in it). A few clarifications:
Your point about iterators and composability: you’re right, the STL suffers a bit here, but Eric Neibler’s ranges library (which is partially approved for C++20) addresses this.
> Compile time programming ... aren’t in a standard yet.
There compile time programming story in C++ has been getting better and better, and has made great strides with recent standards (check out constexpr, which arrived in C++11).
> template meta programming is an agony to use.
Many people would agree with you, but it’s a matter of taste :). It’s “just functional programming” (with an arguably terrible syntax).
I’m very jealous of many rust features, like built in algebraic data types, pattern matching, but most of all their build and package stories.
Your point about iterators and composability: you’re right, the STL suffers a bit here, but Eric Neibler’s ranges library (which is partially approved for C++20) addresses this.
> Compile time programming ... aren’t in a standard yet.
There compile time programming story in C++ has been getting better and better, and has made great strides with recent standards (check out constexpr, which arrived in C++11).
> template meta programming is an agony to use.
Many people would agree with you, but it’s a matter of taste :). It’s “just functional programming” (with an arguably terrible syntax).
I’m very jealous of many rust features, like built in algebraic data types, pattern matching, but most of all their build and package stories.
No, constexpr is in no way similar to the compile-time programing available on Rust. It's not on the same ballpark, it mostly does not have similar goals.
> It’s “just functional programming”
Well, yes, it's just functional programming where all the data is text, you don't get to query or debug intermediate values, and functions aren't even first class.
That's to say, it's a pure language, and lacks all the other features of modern FP languages.
> It’s “just functional programming”
Well, yes, it's just functional programming where all the data is text, you don't get to query or debug intermediate values, and functions aren't even first class.
That's to say, it's a pure language, and lacks all the other features of modern FP languages.
Constexpr is not bad, but not macros. Macros can do things like generate new types, and add methods existing types. That's crucial for things like custom derive. C++ will get that sort of thing from the metaclasses proposal, which slouches towards WG21 to be born:
https://www.fluentcpp.com/2017/08/04/metaclasses-cpp-summary...
https://www.fluentcpp.com/2017/08/04/metaclasses-cpp-summary...
I'm curious, what would it take to get you to completely ditch C++ in favor of Rust?
Completely? I’m not sure. I need to try more Rust. But not having to write another CMakeList sounds pretty good for starters :)
Rust makes things that are technically possible to do in C++, easy to do.
Rust makes things that are painful to do in C++, pleasant to do.
Rust takes your template compiler errors, and gives you beautiful type parameter errors with underlines.
Rust takes your scary macro and turns it into a sanitary macro.
Rust takes your crazy diamond of death and towers of inheritance, and gives you traits, associated types, and composition.
Rust takes your runtime variant and insane union and gives you real sum types.
Rust takes your ASAN, Valgrind, debug alloc builds, and hopes and prayers, and gives you a compiler that let's you use memory safely.
Rust is like C++ without the jagged edges, the specification no one understands, removes the dangerous bits that no one person can keep in their head, and trades the useless abstractions for nice functional abstractions that actually are an improvement over writing C.
Rust makes things that are painful to do in C++, pleasant to do.
Rust takes your template compiler errors, and gives you beautiful type parameter errors with underlines.
Rust takes your scary macro and turns it into a sanitary macro.
Rust takes your crazy diamond of death and towers of inheritance, and gives you traits, associated types, and composition.
Rust takes your runtime variant and insane union and gives you real sum types.
Rust takes your ASAN, Valgrind, debug alloc builds, and hopes and prayers, and gives you a compiler that let's you use memory safely.
Rust is like C++ without the jagged edges, the specification no one understands, removes the dangerous bits that no one person can keep in their head, and trades the useless abstractions for nice functional abstractions that actually are an improvement over writing C.
I'm a big fan of Rust, and seeing that Carmack writes it gives me warm fuzzy feelings.
But this link is mostly content-free, and I don't think it's the type of link that inspires useful discussion. I wish that HN had downvotes on posts, though I suppose it uses several other methods.
But this link is mostly content-free, and I don't think it's the type of link that inspires useful discussion. I wish that HN had downvotes on posts, though I suppose it uses several other methods.
Reading your comment 42 minutes later your fear that it would not inspire useful discussion appears to be demonstrably unfounded.
Flagging a post out of fear of what might be said in response to the article is the aspect of HN culture I find least admirable. Flagging a post is not the same as downvoting. Flagging amounts to "I believe this article should not be read by anyone from HN" because a fully flagged Article (flagged by more than one person) removes the source link from view. That's a pretty serious statement to feel you can make on behalf of all the other readers of HN, and should be used only with the greatest of care.
Flagging a post out of fear of what might be said in response to the article is the aspect of HN culture I find least admirable. Flagging a post is not the same as downvoting. Flagging amounts to "I believe this article should not be read by anyone from HN" because a fully flagged Article (flagged by more than one person) removes the source link from view. That's a pretty serious statement to feel you can make on behalf of all the other readers of HN, and should be used only with the greatest of care.
Huh? Maybe the headline is content-free, but the tweet it links and reply tweet could be useful esp for someone on the fence about Rust, and not just because of the person replying
Flags are roughly equivalent to downvotes.
What is meant by people look to other languages more often because you can do something in C++?
/My rephrasing, but I can't parse Carmack's point
/My rephrasing, but I can't parse Carmack's point
I believe his sentiment was that you can do too much in C++, so you pick other languages because of restrictions they impose, or features they lack.
I would imagine that Carmack specifically means that you can do unhelpful things, like dereference null pointers, access uninitialised memory, accidentally invoke undefined behaviour, etc. A foundational goal of Rust is to make those things nigh-on impossible.
And through those restrictions, like only allowing Send types to be passed between threads, you can even gain features over more permissive languages, i.e. datarace free code.
From " people don’t look to other languages because you can’t do something in c++, but more often because you can."
For me it comes across that you can do in rust what you can do in c++.
As for any reasoning for the change, be it learn-play, a more sane way of doing things, flavour of the month, or something else entirely.
I had a look for a rust vs C++, having limited exposure of the later and none of the former, and found this comparison insightful https://www.apriorit.com/dev-blog/520-rust-vs-c-comparison
But you may be right, if your referring to some unsavory aspects of c++ that rust may well avoid or obfuscate away from the coders train of thoughts.
For me it comes across that you can do in rust what you can do in c++.
As for any reasoning for the change, be it learn-play, a more sane way of doing things, flavour of the month, or something else entirely.
I had a look for a rust vs C++, having limited exposure of the later and none of the former, and found this comparison insightful https://www.apriorit.com/dev-blog/520-rust-vs-c-comparison
But you may be right, if your referring to some unsavory aspects of c++ that rust may well avoid or obfuscate away from the coders train of thoughts.
I read it the same way. C++ is notoriously overstuffed with features.
For anyone else looking into Rust, I've been experimenting in it for the past month. Heres my thoughts:
It advertises itself as a systems level language for the new age, but to me it feels like a functional language in disguise. The borrow checker enforces an unchained functional style (You may have 1 unique mutable reference or may have many immutable references). This sounds bad if you're used to pointer slinging, but once you get used to the peculiarities I find it forces me to write better quality code.
The package management is the most refreshing feature. It's a pain to rewrite my personal libraries in each language I use, but I found that most of the things I needed were already available at higher quality than I would have done. (And using them is a single config line).
For a 'low level language' they make it pretty hard to do some 'low level' things. Example's I've found include:
* writing a doubley linked-list. Consensus is "don't use linked-lists. The stdlib has better tools
* Reading a file into a struct. They make it surprisingly difficult to say "hey, read these 10 bytes, its this struct". Consensus is to use more structured file formats like json or protobufs. There's libraries for reading those things.
Error handling is similar in style to Go, but it get rid of a lot of the boilerplate. The '?' operator effectively acts as if err return err.
The macro system is really nice. I don't write many macros, but it does mean I can use other people's powerful macros. My favorite are 'include_bytes!', 'lazy_static!' and 'dbg!'. The new procedural macros are pretty wacky, essentially allowing you to parse or rewrite the AST. A powerful example I've seen is static checks; This [0] example writes a compile time check to assert that structs do not contain a member named 'bees'.
Overall it's been a fun language to mess around with.
[0] - https://tinkering.xyz/introduction-to-proc-macros/
It advertises itself as a systems level language for the new age, but to me it feels like a functional language in disguise. The borrow checker enforces an unchained functional style (You may have 1 unique mutable reference or may have many immutable references). This sounds bad if you're used to pointer slinging, but once you get used to the peculiarities I find it forces me to write better quality code.
The package management is the most refreshing feature. It's a pain to rewrite my personal libraries in each language I use, but I found that most of the things I needed were already available at higher quality than I would have done. (And using them is a single config line).
For a 'low level language' they make it pretty hard to do some 'low level' things. Example's I've found include:
* writing a doubley linked-list. Consensus is "don't use linked-lists. The stdlib has better tools
* Reading a file into a struct. They make it surprisingly difficult to say "hey, read these 10 bytes, its this struct". Consensus is to use more structured file formats like json or protobufs. There's libraries for reading those things.
Error handling is similar in style to Go, but it get rid of a lot of the boilerplate. The '?' operator effectively acts as if err return err.
The macro system is really nice. I don't write many macros, but it does mean I can use other people's powerful macros. My favorite are 'include_bytes!', 'lazy_static!' and 'dbg!'. The new procedural macros are pretty wacky, essentially allowing you to parse or rewrite the AST. A powerful example I've seen is static checks; This [0] example writes a compile time check to assert that structs do not contain a member named 'bees'.
Overall it's been a fun language to mess around with.
[0] - https://tinkering.xyz/introduction-to-proc-macros/
> They make it surprisingly difficult to say "hey, read these 10 bytes, its this struct".
It's really easy, but it's `unsafe`:
I think something very interesting has happened with `unsafe` in the Rust community as the language has grown. There are lots of things that are "easy with `unsafe`", but everyone seems to round that up to "hard". I think that's a Very Good Thing, because it means that safe code is powerful enough and convenient enough that not using it is seen as a big deal.
It's really easy, but it's `unsafe`:
let my_bytes = [...];
let my_struct = unsafe {
std::mem::transmute(my_bytes);
};
It's necessarily unsafe, because Rust has no way of knowing what invariants the struct is responsible for upholding. If the struct contains a Vec, for example, then transmuting it from bytes will probably give you garbage pointers and a security vulnerability.I think something very interesting has happened with `unsafe` in the Rust community as the language has grown. There are lots of things that are "easy with `unsafe`", but everyone seems to round that up to "hard". I think that's a Very Good Thing, because it means that safe code is powerful enough and convenient enough that not using it is seen as a big deal.
slice::from_raw_parts_mut makes it trivial to reinterpret some bytes as a struct. Beware, though, that Rust doesn't have a stable ABI as C does, and can reorder fields and do other magic freely. What you're doing is already incredibly dangerous in C, but it's extra dangerous in Rust.
By the way, regarding doubly linked lists, standard practice when dealing with anything that doesn't fit nicely with Rust's ownership model is to either (a) write a nice high-level abstraction over it that uses unsafe code, or (b) give up and use array indices to circumvent the borrow checker, which has the added bonuses of having smaller "pointers" and much better locality.
By the way, regarding doubly linked lists, standard practice when dealing with anything that doesn't fit nicely with Rust's ownership model is to either (a) write a nice high-level abstraction over it that uses unsafe code, or (b) give up and use array indices to circumvent the borrow checker, which has the added bonuses of having smaller "pointers" and much better locality.
> to me it feels like a functional language in disguise
I disagree on the "disguise" part.
Yet, I don't see why you consider it an opposite of being a system language.
I disagree on the "disguise" part.
Yet, I don't see why you consider it an opposite of being a system language.
Maybe I'd call Rust a "resource-constrained" language than low-level. The usecase being you want no GC, for memory inflation or CPU pauses, but suffer a bit in overall CPU time and memory locality.
Why do you think you'd suffer in CPU time and memory locality?
Sometimes you have to put stuff behind pointers, use runtime checks for safety, or change how you represent data to something suboptimal.
Sure, but the inverse is true too. Sometimes you can get better performance in Rust, because the borrow checker allows you to do things you would never do in C/C++. For example, passing around array slices is very common in Rust, but the cases where you'd do it in C or C++ is much more limited, because it's so error prone. You have no guarantees that the memory being pointed to won't be pulled out from under your feet.
I've had Rust hinder my ability to pass around array slices more than help.
The point is that while it's easy to do in C++, it's extremely error prone.
Rust encourages you to write code that works with things like slices and references instead of copying, because the compiler won't let you use them in a way that is error prone.
Rust encourages you to write code that works with things like slices and references instead of copying, because the compiler won't let you use them in a way that is error prone.
That's some nice sounding evangelism, but also completely besides the point. You seem to think I don't understand C++ and Rust. Believe me, I do. Rust inhibits use cases of array slicing that are both useful and not error prone.
Last time I read a blog post about this, they picked a situation that was perfectly safe and ordinary in C++, with straightforward function-local safety reasoning.
Last time I read a blog post about this, they picked a situation that was perfectly safe and ordinary in C++, with straightforward function-local safety reasoning.
Just out of curiosity, do you have an example where Rust got in your way when trying to use an array slice?
Approximately, here is one example. I wanted to use something like a Vec<&[u8]> as a means of passing a set of buffers to a function. Likewise it made sense to return such a value from another function, g. The way g worked was to read or hold a large buffer into memory, e.g. 1 megabyte in size, and then parse out the slices from it. So maybe you'd want to return something that looks like the C++ type
Probably some others would be uses of Interval<Buf>, and some cases where functions return Buf in https://github.com/srh/nihdb . I wanted to use &[u8] to represent the bounds of intervals, and IIRC that generally involved passing intervals upwards into functions, but for some reason I can't remember it got annoying and I couldn't be bothered to do it.
struct Foo {
private:
vector<uint8_t> buf;
public:
// points into buf
vector<pair<const uint8_t *, size_t>> slices;
};
Well you can't do that. Obviously there are workarounds, like to return an object of type Foo holding the buf, with an api like impl Foo { fn getSlices(&self) -> Vec<&[u8]> }. Internally the object holds a Vec<(usize, usize)> or something like that, and you have a bunch of translation logic around your API's. And extra work to allocate the return value. So that's one example of Rust getting in your way.Probably some others would be uses of Interval<Buf>, and some cases where functions return Buf in https://github.com/srh/nihdb . I wanted to use &[u8] to represent the bounds of intervals, and IIRC that generally involved passing intervals upwards into functions, but for some reason I can't remember it got annoying and I couldn't be bothered to do it.
This is a pain point, but it’s not due to slices; it’s the “self-referential struct problem.” There are various solutions, as you note, but it can be annoying, it’s true. It’s a tough one; in the general case, it’s saving you from problems, but when you know you’re not going to hit those edge cases, it’s less than ideal.
Is there a theoretical reason for the "self-referential struct problem", or is it just an artifact of the current borrow-checker implementation?
It doesn't seem unsafe to have a struct field refer to another member of that struct, but maybe I'm missing something.
It doesn't seem unsafe to have a struct field refer to another member of that struct, but maybe I'm missing something.
I didn't say it was specific to slices. The inconvenience around array slices is because they have a pointer. The non-pointery aspects of Rust slices are an advantage over C++. (E.g. not having pointer arithmetic.)
As soon as I saw it was just a quote, I had this gut instinct it would be Carmack speaking. The reason is because when DirectX was really marketing hard, Carmack did some work in OpenGL and stood up for it. It was kind of important to me since it was open.
[deleted]
I have never done anything in Rust. Are there any benefits? Any drawbacks?
If you don't mind a bit of a re-education, Rust allows you to write programs that are as fast as those written in C or C++ and you'll be more confident that they don't have strange memory bugs. (Reason: the ownership model of Rust prevents a lot of bugs that are found in C or C++, but this model also rejects many correct programs, so you need to re-learn how to write some programs.)
One thing people (like me ) often say is that it forces you to “learn to write better.”
One way you can think of it is this:
- Rust rejects some programs that are truly wrong (`“2” + 3` style)
- Rust rejects some programs that really are just fine. They are actively improving this with stuff like “Non-lexical lifetimes.”
- Rust rejects some programs that are fine _how they are now_, but which are hard to keep fine when you change them. This is part of why linked lists are hard to write in Rust :-)
One way you can think of it is this:
- Rust rejects some programs that are truly wrong (`“2” + 3` style)
- Rust rejects some programs that really are just fine. They are actively improving this with stuff like “Non-lexical lifetimes.”
- Rust rejects some programs that are fine _how they are now_, but which are hard to keep fine when you change them. This is part of why linked lists are hard to write in Rust :-)
I admit that I'm very unexcited about non-lexical lifetimes—the cost-to-benefit ratio seems worse than the current model. The current lifetime system is simple to understand: a value is live until its owning variable goes out of scope. It can be a bit restrictive sometimes (though it seems that after a while you learn how to work around those issues easily enough), but it's easy to keep in your head and reason about code. With non-lexical lifetimes, I think that we'll have a harder time understanding lifetimes, it'll be more difficult to teach them and to debug them.
My understanding is that the semantics are exactly the same, but it’s just possible for the checker to match your intent more tightly.
For example, in lexical lifetimes, this fails:
For example, in lexical lifetimes, this fails:
let x = &mut foo;
if x.bar() {
baz()
} else {
foo.consume() // takes foo by value
}
It will complain that you’ve used `foo` while it is borrowed by `x`, even though a human can easily tell that `x` is already irrelevant by that point. NLL would accept this (I believe).If you don't mind going through a 40-page free e-book from O'Reilly, I can strongly recommend this: https://www.oreilly.com/programming/free/why-rust.csp (Direct link: https://www.oreilly.com/programming/free/files/why-rust.pdf)
It explains the concepts and strengths of Rust in a very easy-to-understand way. (Jim Blandy is also the Author of the O'Reilly Rust book.)
It explains the concepts and strengths of Rust in a very easy-to-understand way. (Jim Blandy is also the Author of the O'Reilly Rust book.)
It is a great language. You will fight with the compiler, it will do its best to tell you whats wrong, and finally when it fully compiles with no errors, you pretty much never have to worry about the program in runtime.
I read this nearly four years ago, and it convinced me (based on a lot of experience with bugs in concurrent code) to dedicate a significant portion of my spare time learning and using Rust: https://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.h...
Here's a great talk (from Strange Loop conf 2017) by David Sullins about Rust concurrency:
https://youtu.be/oIikwmeGVYY
https://youtu.be/oIikwmeGVYY
One cool thing is that you can use it almost anywhere -- an OS kernel all the way up to a game compiled to WebAssembly to run in the browser.
Yes
Even that's not true.
One of the practically valuable things about Rust is that it has a decent and standardised package manager and build tool. C++ does not, and is nowhere close to having that.
Another is that the Rust libraries use iterators as a straightforward, composable, and efficient way to write operations on streams of values. The C++ standard library doesn't have anything like that.
Rust has a really powerful macro system. C++ doesn't. It has template metaprogramming, which can do most of what macros can, but it's agony to use. C++ is getting some really powerful compile-time programming facilities, but those aren't in a standard yet.
There are a lot of things in Rust that are in C++. There are things in C++ that are not in Rust (some of them even useful!). But there are things in Rust that aren't in C++, and when C++ loyalists say there aren't, they just look stupid.