Sayonara, C++, and Hello to Rust
thecodedmessage.com
thecodedmessage.com
Sure you can do everything you'd want to do in C++ in Rust, but the developer experience when actually writing code is agonizing most of the time. I deeply miss C++ templates in most other languages, excepting fully dynamic languages, and D. Rust is, to me, more of a replacement for places I'd write C, and be in a C mindset. C++ (well-written, modern C++) is so much more than C, and so much more than people give it credit for. A true C++ contender would have to pass not just the C bar but the C++ bar as well, rather than looking at C++'s strengths over C and saying "actually those are bad things" like it's been popular to do for the last 10 years.
Speaking of replacing C, Rust is not without its strengths: the one place I find Rust truly a joy to write is OS dev, and similar low-level projects, in which the concepts and limitations of the language map to quite nicely to the expectations of the system. It seems like the obvious candidate for these types of applications (including embedded development) but there's little surrounding literature - it always baffles me that most people seem to be using Rust to write ... web services?
I think it's the whole x-rewritten-in-rust-now-faster hype train. Although those limitations you mention, what's easy and hard to do, do have a more or less automatic impact on performance, fueling that train.
I think of it like this:
Programmer level 1: Makes something work in a higher level language
Programmer level 2: Makes something work in a lower level language
Programmer level 3: Makes something fast in a lower level language (in most cases just a few simple optimisations on top of #2)
Programmer level 4: Makes something fast in a higher level language (object pooling, understanding how the GC and VM works to avoid GC pauses at the wrong time and all that)
So with Rust (or yes, C++, though it's a bit more of a foot gun IMHO), level 2/3 people can write code that's efficient. What I think is somewhat unique about Rust is that it keeps programmers from making lots of common mistakes working with lower level languages - like it's designed for level 1/2 folks.
Then there's the plethora of other advantages you get from its great type system and inference etc.
I think some C/C++ devs take a perverse pride in their language not being suitable to write web servers, because webdev is not real programming anyway (excepting the 'systemsy' parts like nginx).
Well, Rust is great at systems, great at web servers, pretty good at one-off script, even pretty usable for frontend. Every time I hear someone pigeonhole it as just a systems language, I think - you're missing out.
Btw. Who likes C++ templates? They are such a roundabout way to program the compiler that it regularly hurts me. You need to understand a set of arbitrary rules (steered by implementation of the existing compilers, not language design, it seems) to make the compiler do what you want in many lines of code. And only fellow template lovers will have a chance to understand your intention. It is admittedly powerful, but compared to any decent macro implementation (Lisp, Haskell, OCaml, Rust) it is so much worse...
Saying I like them is a stretch, but I prefer to write a C++ template rather than a macro (C++ or Rust) any day.
I don't see why decidability should render them incomparable.
Exactly, they are macros that can only be invoked via some weird implicit entry points. Proper macros could be used very much in the same way, of course, if the compiler recognizes their output correspondingly. You could, for instance, use macros to generate traits/type classes to enhance overloading. Why someone would prefer this code generation to be so implicit, I don't understand.
> I don't see why decidability should render them incomparable.
Of course you can compare them if you want to point out that particular difference. But if you claim that templates are more powerful, the obvious answer is: Sure, because they're not type safe. Templates simply aren't a type system extension, they're more like macros.
Could you elaborate in which way C++ templates are not type-safe? Does the upcoming concepts feature alleviate any of it?
C++ Templates are not type safe because they need to be evaluated before you can make any statement about their successful, correct application. This evaluation might not terminate. Macros habe the same issue, of course, but they explicitly give you a Turing-complete programming language so it feels more natural.
You might want to consider a setup where the library author is not the same person as the library user. Don't you think it's important that the former sees and not the latter? That's why linker errors and compiler errors and template errors are not equal. Also, if you make an error in a template or its instantiation you might not even get an error but an infinite (i.e., maybe aborted) computation. If you don't see the difference between these things, I cannot help you.
They work very well and are quite pleasant to work with.
I don't really see what's so complicated about them.
If you have proper macros you don't need to know the compiler internals. You have an API that generates code with the language semantics you already know. By definition that's simpler. It's also more powerful, btw.
What exactly is your perceived problem? You need to have the template definition in order to instantiate it; that is pretty logical. Is this kind of basic common sense what you call "knowing the inner workings of the compiler"?
The first step prior to overload resolution is name lookup, which discards template candidates whose declaration refers to dependent names on template parameters which wouldn't exist for that implicit instantiation.
The only order that matters is what specializations are visible at the time of the instantiation, and adding more specializations after that is more likely than not to lead to ill-formed programs (due to the ODR rule that you must satisfy), so you can assume it doesn't happen.
enable_if, recursion or whatever else you're referring to are merely trivial applications of the rules.
I don't think it's unreasonable to have to understand how overloads are selected in order to program in C++. The semantics were not designed to match an implementation but rather to make the most sense. This has nothing to do with compiler internals; if anything many compilers implemented it incorrectly for decades.
0. https://github.com/ThePhD/sol2 1. https://github.com/ThePhD/sol2/blob/develop/include/sol/func...
The error messages in particular - so very expressive.
Once you move past the prototyping stage on to full on production coding, I totally buy that in a lot of cases, the safety that Rust provides (if it compiles, it will almost certainly either panic or work, ignoring logic bugs), is a big win, but I'm not convinced yet for experimentation when prototyping...
So, The first draft is going to take like twice as long, but every subsequent iteration on your prototype will be much faster. I find the payoff time to be pretty quick (like one or two iteration are often enough), but obviously if you're doing a true one-shot that you're never gonna touch again, it won't be worth it (that being said in my experience, such situations are pretty rare and most one-shots end up being way more than what they where supposed to in the first place).
C++ is probably at the other end of the spectrum since it's too easy to accidentally cause a memory error during refactoring. C# and Java are a bit in-between, the automatic memory management limits the footgun, but the type system is much less powerful than Rust's one so there still a lot of issues that can come during a refactoring. I have only very limited OCaml experience and no Haskell experience but I think you'd have most of Rust advantages with those, except the thread-safety part which is my favourite part of Rust but is also situational.
(Safe) Rust has Data Race Freedom, which means you get Sequential Consistency. As I understand it, Haskell does not promise you data race freedom, so, if you mistakenly implemented a data race now you don't have Sequential Consistency and now you can't reason about non-trivial programs at all.
I think Haskell, like Java, and unlike C or C++, promises you still get a meaningful program if you have a data race, but it may be tricky to debug the mess.
Edited to expand: Suppose you're writing a program to process 16GB of data, the result will be a floating point value between 0 and 100.0 as a result of this processing. You realise it's embarrassingly parallel and so you spin up 16 threads to solve the problem in parallel on your CPU cores.
Mistakenly instead of processing 1GB per thread, you process the same 1GB on all sixteen threads, the processing involves a lot of twiddling data, and so now it's full of data races. What is the result of your buggy program?
In Rust, the program doesn't compile, or depending how you wrote it, exits immediately reporting that you can't go around processing the same data from different threads.
In C or C++ maybe it deletes all your data, more likely it simply exits instantly with no result, spins forever, or outputs something completely spurious.
In Java you might get an Exception reporting that you mustn't do this, but you might get a bogus but real looking answer like 46.2 now good luck figuring out why.
I don't know for sure about Haskell, but I think you get a similar situation to Java minus the exception.
I don't think this is correct. Safe Rust includes atomics that are data-race free but need not be sequentially consistent.
However you can indeed use it from Safe Rust, and you can choose Orderings which do not give you Sequential Consistency if you're comfortable with that, or more likely because you are implementing an algorithm whose authors assure you this is fine (I hope they're correct). I think that's a very modest caveat to the overall promise of Sequential Consistency, but sure, it is a caveat. If you write code that explicitly asks for Ordering::Relaxed and are astonished you don't get Ordering::SeqCst I guess I apologise for confusing you.
This is true (when you insist on mutable shared memory), but in Haskell the safety guarantee is in fact not so far from Rust. The Haskell community is well aware of concurrency issues very early, and they developed a dozen of composable abstractions to solve these problems. (IMHO Rust is the only one that has a truly novel solution in recent years.)
In Haskell, either you have to use `unsafe` functions to share bulk of memory or you don't share memory at all (as mutability is a pita in Haskell). On top of these unsafe functions, you can build other safe abstractions, just like Rust. These abstractions are often available as higher-order functions in libraries, making them highly composable and reusable.
E.g., the canonical memory-sharing data type is `TVar`, which must be access from the `STM` monad [1], guaranteeing both atomicity and data race freedom. (BTW, STM stands for software transactional memory if you aren't familiar with it.) A major offender is `IORef`, and fortunately Haskell provides atomic operations on `IORef`s.
GHC Haskell also offers free parallelism for all pure computations via the `Eval` monad [2].
> Mistakenly instead of processing 1GB per thread, you process the same 1GB on all sixteen threads
This is highly unlikely in Haskell. Haskell emphasizes on composable combinators, so a program that does not share memory would probably look like
foldr accumulateFunc initValue . mapConcurrently processBatch . split batchSize $ data
(mapConcurrently here is a real function [3].) I'm sure C++, Java, and Rust have similar abstractions, but imperative languages make it too easy for programmers to do `for` loops, making it much more error-prone.Several array libraries provide safe parallelism. The interface is similar to Rust rayon, but the way they work differs greatly. For example, in Repa [4] you can do
computeP $ traverse inputArray newShapeFn (\getElem curIdx -> ...)
Here `traverse` results in a "delayed" array: the results are not immediately available, and what you are doing is just composing array operations. `computeP` forces the computation to happen _in parallel_. Since GHC is very capable of optimizing intermediate data structures away, this code can compile to very efficient machine code -- there is no mutability at all, yet you get very safe and efficient parallelism.PS: what Haskell is really bad at so far is safe in-place updates for arbitrary data types (like custom ADTs), which is solved by Rust cleanly. GHC 9.0 added Linear Types [5] to address this problem. Haskell and Rust are both bad at safe in-place updates of arrays, vectors, etc., and are likely to be bad for a long time -- their indices are arbitrary integers, which cannot be analyzed statically at compile time. Haskell probably can solve this problem completely when it gets dependent types [6], whereas Rust is unlikely to support DT any time soon, if ever [7].
[1] https://hackage.haskell.org/package/stm-2.5.0.1/docs/Control...
[2] https://hackage.haskell.org/package/parallel-3.2.2.0/docs/Co...
[3] https://hackage.haskell.org/package/async-2.2.4/docs/Control...
[4] https://hackage.haskell.org/package/repa
[5] https://github.com/ghc-proposals/ghc-proposals/blob/master/p...
[6] https://github.com/ghc-proposals/ghc-proposals/blob/master/p...
Have you tried Zig? Comptime templating it one of its selling points.
As far as C replacements go, Zig has my vote for the most approachable but also the most well thought-out. I couldn't sing its praises enough.
std::mutex m;
void good()
{
std::lock_guard<std::mutex> lk(m); // RAII class: mutex acquisition is initialization
f(); // if f() throws an exception, the mutex is released
if(!everything_ok()) return; // early return, the mutex is released
} // if good() returns normally, the mutex is released var m: std.Thread.Mutex = .{};
fn good() !void {
m.lock();
defer m.unlock();
try f();
if (!everything_ok()) return;
}
It's still the RAII pattern. The explicit call to deinitialization does not disqualify it. I can overcomplicate it with a LockGuard class too: const LockGuard = struct {
mutex: *std.Thread.Mutex,
fn init(mutex: *std.Thread.Mutex) !LockGuard {
mutex.lock();
return .{
.mutex = mutex,
};
}
fn deinit(lg: *LockGuard) void {
lg.mutex.unlock();
}
};
fn good() !void {
var lk = try LockGuard.init(&m);
defer lk.deinit();
try f();
if (!everything_ok()) return;
}
In this case it demonstrates an improvement over C++, which is what happens if an error happens during a constructor. In C++ throwing an exception in a constructor is essentially UB; in zig it's a regular function call just like anything else.https://en.wikipedia.org/wiki/Resource_acquisition_is_initia...
Regarding errors in a C++ constructor: https://isocpp.org/wiki/faq/exceptions#ctors-can-throw
That's what Rust actually is useful for. And what C++ should have been used for. It has grown in the abomination it is, because the people (well, yes, me too ;) abused it as language for 'everything'. I do actually fear that the same will happen to Rust.
Btw. most people use Rust to write web services because most people actually write web services ;)
I am curious, what are some actual things you have done with templates that you couldn't do with generics? I'm pretty firmly in the camp of "most of C++'s strengths over C are bad". So I'm struggling.
I'd maybe possibly say C++ templates are better for math? Which is true. Rust generics are not great for complex math. However eigen is nightmare monster that absolutely ruins my compile times so I'm not sure whether this counts as a win or loss for templates. Maybe a bit of both.
Rust is still duper bad at UI. Even if there are some imgui-like Rust libraries doing cool things. I've never seen a C++ GUI library that didn't suck horribly. But at least it was possible.
Most large C++ codebases that have been running in production over decades are unfortunately a Frankenstein's monster written in a mix of old style C with classes C++ code full of malloc and free footguns, heavily OOP driven prefer-inheritance over composition era code full of virtual destructor footguns, and the modern nicer RAII std::unique/shared_ptr memory management based C++ code. And a lot of template and macro magic thrown in across each of those eras.
So you can get really productive in modern C++ pretty quickly and it can be an enjoyable experience. But to maintain a large old C++ codebase you are going to have to grok a lot of stuff. So much that I really can't find too many people who can hold it all in their head. I certainly can't.
Finally, am not sure if there was a hint of derision about Rust being primarily used for web services but I think it's a great thing if the next generation of web services will be developed in something as efficient as Rust rather than more resource hungry counterparts like Python or JS. Good for the balance sheet. Good for the environment. Good for the web devs who get expressive easy to understand code with a lot of guard rails built in.
> what you save on the computer center gets spent in developer workstations.
um, that cat is already out of the bag. unless you work on mobile or web frontend most backends are big, ugly, have a lot of dependencies, running them needs ALL the RAM anyway :)
Building from scratch Gentoo style not so much.
I seldom compile third party dependencies.
After a year of writing Rust for a client for a virtual world, some comments.
For this problem, the server is constantly feeding you UDP packets full of changes to the scene, and you're constantly fetching assets from other servers using HTTP. All this has to happen while updating the screen at 60FPS or so. It takes several CPUs and a GPU working hard to show a detailed 3D world properly.
* Safety is a big deal. I haven't had to go into a debugger in the last year. That's a huge win.
* There's a lot of concurrency involved, and being free of race conditions is a big help. Rust does not help with deadlock avoidance, though. That's the next frontier in concurrency - static deadlock avoidance analysis. There are people working on this for Rust, but it's a hard problem.
* Rust has a big problem with certain data structures. In particular, if you have a single ownership tree, you cannot get from a child to a parent. If you want to have backlinks, you have to reference count everything. The language needs something like "single owner with weak backlink, made safe at compile time". That's a hard problem. I keep ending up with refcounts forward and weak refcounts backwards. With refcounts, if you borrow something twice, you get a panic at run time. The compiler cannot catch those at compile time. So borrowing has to be very short term.
* Rust wants functional programs. If what you're doing fits into that model, everything goes smoothly. When there's a lot of state, it's harder. The state here is a whole 3D scene, so it's inherent in the task. In server side web stuff, most of the state is in the database, so the application programmer doesn't have to deal with maintaining its consistency.
* You can paint yourself into a corner with data structures. Figuring out where you need reference counts, where you need locks, and where you need interior mutability has to be right. And you can run into problems such as "that struct can't have the 'send' attribute because it has an element which, deep down, contains a reference count". Make a mistake and rework is difficult. Data structure design is a puzzle-solving problem.
* "Async" has most of the same locking restrictions as threads. It's not like Javascript, where you never explicitly lock. Unless you have some huge number of threads, async isn't much of a win. It's really for the use case of a server with a huge number of slow clients. You probably shouldn't be using Rust for that anyway. That's Go's ideal use case.
I have some arguments with the Rust designers. But really, it's a big advance over C++. It's not a panacea. As I keep saying, do your web server stuff in Go. Go has a more user friendly memory allocation model, a more user friendly threading model, and well-exercised libraries from Google for things web servers do. Rust is for the hard problems.
Maybe this is a nitpick, but the need to say it suggests you might not understand something important. (Safe) Rust has data race freedom not freedom from race conditions. The most likely reason to muddle those is not knowing what the difference is, which is important because race conditions are an unavoidable phenomenon in computer systems (and indeed the real world) whereas data races are something very strange and difficult to reason about.
If you put the cat out the front door, then walk to the kitchen and close the kitchen door, you can't be certain the cat is still outside, it might have run around and back inside. That's a race condition. Rust doesn't inherently care about those, so if you do then you need to program carefully to handle them. Data races aren't like that, they don't have a clear real world analog, perhaps they're as if you could discover that sometimes while you're putting the cat outside, somebody else is simultaneously putting the same cat inside your house - except, that doesn't make much sense. Nor do data races, which is why it's important that Rust prevents them while many languages just make that your problem.
GhostCell and its variations (QCell, LCell etc.) are meant to address this without resorting to Unsafe Rust. But it's a bit of a hack so far, and yes it would be nice to have a more elegant, language-level solution.
Then there's the problem of you got there by following links, and now you want to mutate something. Chasing links is fine if they're read only, but if they're mutable, you have to make sure you don't have a loop of mutable references. Again, this is a non-local analysis kind of problem.
For one, pulling down a package is a one liner. It just works, and I haven't come across a special one that does its own thing yet. The ecosystem seems pretty complete, too. I've yet to have to write my own component: everything from serialisation to async runtime, queues, config parsers, rate limiters, hmac, logging and so on is there.
The big thing though is debugging. I'm CPP is possible to write bugs that manifest in very strange ways when you run the program. It eats up days to debug, and you're never sure if it's actually solved, because there's a large surface that might contain the bug. With rust it will complain at you at compile time. This can be confusing but you'll figure it out eventually and then it's fixed.
Real time multiplayer game side project, and a trading application.
So stuff that needs at least some performance and garbage collection would be something with a real cost.
It's not quite as bad as NPM land where some people add a dependency for left padding a string, but dunno...
Probably a fairly unpopular take, but I kinda always liked that about C/C++ code bases (that avoid hidden dependencies): It makes you realise the cost of dependencies :P
So while Rust eliminates a lot of foot guns from C/C++, IMHO it introduces a major one.
I'm more and more finding myself either either looking for less-popular and more minimal crates (that don't need Async, or tens of other further deps), and even writing my own things for certain simple stuff (the latter often reinforced by wanted to experiment and learn).
I'm more concerned about many 'critical' (because there's nothing like them in the standard library, i.e. 'rand', 'chrono') and well-recommended crates are still not at version 1.0.0, so technically they could change the API... (they won't/can't, but seems a very odd situation to me).
Pretty much everyone's using chrono and rand, as there's not really anything else...
Tokio is the de facto async runtime, used by many (though not as many as rand and chrono surely, but still), and they've bumped major versions. Some projects still use old versions, which is actually much more problematic since multiple async runtimes usually can't exist simultaneously, but things are still mostly fine overall.
Learning Rust is a major time investment. Basic Javascript? Not so much.
They'll just draw different crowds.
A lot of crates have a bunch of selectable features. Their defaults usually lead to many dependencies needed. If you carefully select those, you can cut down on dependencies a lot.
I've seen cpp codebases where people just copy paste the libs because they don't like struggling with cmake. Then there's boost, which is huge. Maybe a bit less relevant in recent versions but still, once you need it...
From a security perspective you may have a point, if it's security critical maybe think about that. But then also security benefits from rust in other ways.
My main focus is ergonomics: how simple is it to grab a lib, how often do unforeseen issues happen? With CPP I just get this feeling every time that this is a cave entrance, and adding this lib that I know I need might just eat up a whole day of googling. It doesn't happen every time of course, it just has happened enough times that I'm fearful.
With Rust it hasn't happened yet. Something in general about it is that errors seem to make sense. You get a message, and the hint is an actual hint that helps solve the problem. This goes for using libs as well as the language in general.
The alternative would be to arbitrarily slow down library development by including them in STD which is silly for many reasons (ie. it would hurt ecosystem, it would hurt the actual library development, it would hurt the STD development).
There are also correctness concerns; it's easier to ensure code is correct if you write it than if you take it from the Internet.
Don't get me wrong, I've read about Rust and it's capabilities vs things like C++ and yes, I can see benefits. That's not what I mean.
It's in 10 years when we clone a repo on Github and it pulls down 2000 dependencies: will they all still work?
Everything nowadays seems to require massive dependencies!
I know we've long used libraries for different bits of functionality rather than reinventing the wheel but it just seems like we're giving up masses of control/agility for convenience that may kick the shit out of us later!
Just a thought...
Most likely no. This is why Rust has its own repository of immutable packages on crates.io. Depending directly on Github is indeed a terrible idea.
Ok...
> If you are not, Rust is overkill for your task at hand and you shouldn’t be using it. I earnestly recommend Haskell.
That's... unusual advice. There's a lot to be learned from Haskell, but if I was recommending languages for the application space, there are other languages I might mention first.
But in general, I get the sentiment. For high-level projects, use a high-level language. And if you look at the sad state of js, python, et. al., there is plenty of room for improvement.
Maybe it would even work if someone strapped Rust's type system on go or go's GC on Rust.
Any important codebase should rely on a language for which sufficient talent and community support (StackExchange...) exists, otherwise you add yet another risk to your project.
Haskell uses monads for lots of stuff, which can be unintuitive too. From what I've seen, multicore OCaml will allow you to write programs in direct-style (instead of monadic-style) and still be async, which would be great. I hope multicore OCaml will be that "garbage collected Rust" that many people seem to want.
I actually prefer the Rust approach here as well, removing the single-statement shortcut makes sense, as all it does in my view is cause issues, and I have it baked into my code styles for it not to be used.
Would be very interesting to hear your experience. Most of these articles like OP, come off exactly what you say: just switched, and now a big fan.
You might go from believing that you've found somebody flawless to discovering that actually two weeks with them on a sandy beach or in a hotel room was maybe more exposure to their specific quirks than you are OK with, and you're quite glad to spend a few hours every day not with them - without that meaning you wish you were with somebody else instead.
For example, you might start out very happy that Rust's assignment operators have no value, and so lots of trivial C and C++ mistakes can't happen, but as the Honeymoon wears off, you realise two things, firstly, that once in a while it would be convenient to use values of assignment, and so while on the whole it's better to be rid of them you do miss them, and secondly that some other mistakes are needlessly still present, why didn't Rust fix those too?
This all reflects in job descriptions. They just mention a ton of buzz tech, no matter if they actually use it, if you would actually use it in the job. No description of what you will actually do. This focus on the tech stack is superficial.
E.g. if you were writing a device driver, you might try to choose c over asm wherever you can. If you were writing an enterprise app, you might choose java over c++. Etc etc
The idea is that you don’t want to give up productivity boosters like garbage collection unless you’re forced to.
But it’s just an idea and not everyone buys into it. Otherwise we’d all be writing in the ultimate programming language (lisp).
I like Lisp, but I'd argue that languages like Python and Ruby are higher level. Also easier to read.
Higher level - i think that has to go to lisp and it's because of macros. You can write your own programming language, lisp is more like a toolkit for creating the ideal language for solving your problem in that regard.
I agree with all 4 of those points in that tweet.
You can do some pretty twisted things from the comfort of python, e.g. years ago i was able to create an LD PRELOADable shim in python: https://github.com/CraigJPerry/pyshim
It would be a terrible idea for Foo* to mean std::unique_ptr<Foo>. If you are typing out "unique_ptr" so many times in your C++ code, you're probably doing something very wrong. The C++ unique_ptr controls memory ownership, and you typically only want one owner of a resource. Sometimes it is necessary to transfer ownership of the resource, but it is (or certainly ought to be) a rare occasion in comparison to how often you need to access the resource. I could understand if you are passing shared_ptr around all over your code (although I wouldn't recommend that), but unique_ptr??
Also, raw pointers have not been deprecated in C++. Raw pointers are not a problem when you get them from a unique pointer. Raw pointers can be a source of errors when a single pointer is used to index into multiple objects, but for this C++ now has std::span<T> so you can avoid most of those common errors.
struct Foo{ std::unique_ptr<Foo> Ptr; }
Foo::Ptr foo;
(Personally, I don’t think typing unique_ptr<Foo> or unique_ptr<const Foo> is such a big deal. But then, I also consider having “using namespace std” the only somewhat sane way to use the STL.)
I hope you never ever do that in a header :)
let sandwiches = inventory.filter(delicious_cheeses).map(make_sandwich).collect();
... won't compile because Rust can't infer what sort of container sandwiches is. Is it a vector? A list? Something custom?When C++ can't be sure in some places auto is obligatory anyway, so, too bad, you get what you're given. There are rules for what you get, but you might have reasonably intended something else, and so the effect is surprising.
Does a job in an idiomatic way
std::unique_ptr foo = std::make_unique<Foo>();I’m always impressed that Rust turns people into such devoted evangelists after relatively little experience with it. This is a testament to its quality.
Most of these changes are small paper cuts that make sense to fix, but they’re breaking changes so they need to be opted into explicitly.
For example, earlier use a field x of struct b in a closure would capture the ownership of the whole of b. In rust 2021, the closure will only take ownership of b.x. This makes sense, but it’s a breaking change and therefore it needs an opt-in.
The Edition system is one of Rust’s biggest strengths IMO. Some languages never make breaking changes be enough if they’re small because it would break too much code. Other languages make breaking changes but expect users to migrate, placing a burden on them. The Rust edition system leaves it up to the user, on a per-crate (package) basis. Staying on the old edition is perfectly acceptable, as is moving to the new one.
Further reading - https://doc.rust-lang.org/edition-guide/rust-2021/
Plenty of 'C++ people' have been doing C++ for decades.
It was a new ecosystem trying to figure itself out, and it takes quite a few attempts to do that. Both react and vue are near 10 years old, express is over 10 years, so it’s not “new and shiny” chasing.
Rust will go through the same thing before the dust settles.
Rust might be great (I don't know) but this aspect of the community makes me less likely to try the language, not more.
( I didn't care a bit about this aspect when I approached Rust )
How so?
You need to invest a lot more to use the tool well, but then the tool can do a lot more.
> but then the tool can do a lot more
Can it? How do you measure that Rust/C++ do more than JS/TS given that they're used to solve completely different problems?
That's a whole paradigm shift from using languages that are only fit for a given application domain.
We’ve had basically no alternative other than C, with a lot of failed attempts at half supported C++. A lot of us suffered the horrible experience of working with severely lacking C++ compilers in the embedded space. Unless you were working with ARM or on top of linux, C++ was a bad idea.
Pretty much until ARM, support was terrible.
I quite like several languages I learned, Java, Python, even C# is rather nice in some ways, others didn't make a dent, Scheme I had actually forgotten until I was asked about old Scheme code I wrote, ML was nice but too abstract for me... I am learning PowerShell at the moment for some reason, I can't say I'm fond.
However I never had any inclination to go back and rewrite software. The software already works, why change language? But I've already spent a bunch of time doing that in Rust because it's so much nicer than the C I wrote a lot of software in previously.
It's not just the language itself, which is pleasant, the tooling is excellent as well. I spent part of Sunday pair-debugging a friend's Perl AoC solution (it worked for the test input but not real inputs, that's a fun story) and they lamented that early on they'd forgotten to have revision control. When you ask Rust's cargo for a new project, it automatically creates you a git repository (if git is available) for the project, and if you didn't say you're making a library it gifts you a "Hello, world." example program to start with.
The error messages are excellent. The developers actually put effort into further refining the already excellent error messages. After a long day writing SQL Server queries for which the response is typically "Syntax error" if you got anything wrong like this is still 1975, it's really nice to write a line of Rust that's wrong and have the compiler explain why it's wrong.
The documentation is lovely. The official documentation for the standard library is in the exact same style as auto-generated docs for your own code. Because why wouldn't you do that? Java more or less did this and I appreciated it then, but writing such docs for Java is much more painful, and yet the results are less useful. C# is similar. I believe Swift got nice documentation behaviour in a recent version but I don't know Swift.
I think this is an imagined problem. If you use Foo * for ownership, you will need to write new Foo() + delete foo. I don't really think that's more convenient than just writing std::make_unique<Foo>(). It's more a matter of old habit, I think.
Still, if C++ was designed as a new language today, I think there's a chance that unique pointers would have made their way into the native language specification, together with for instance std::tuple, instead of being library features, like they are today.
Granted it is not yet all there, but so it is with ISO driven languages.
I've been programming C++ professionally for 15 years. My work currently runs on Windows, macOS, and Android. With a little Linux on rare occasion.
I've been hearing about C++ modules for, I dunno, 7 years? Thus far they have had literally zero impact on my both my job and my decision matrix. I do not anticipate they will be actually something I can use until at least 2024. I will be mildly surprised if C++ modules are in broad use in 2026.
I would have said similar things about Rust until recently. I definitely can't replace all my C++ code with Rust. But I can use Rust to write new CLI tools and write certain libraries.
Also clang has supported Apple/Google's point of view for quite sometime with header maps....
If you wanna argue that modules are ready for primetime and businesses should start converting their codebases to use them then go for it. But it's a pretty obvious fact that almost no one is using modules outside of hobby projects. I don't think I've seen a single significant C++ based project linked on HackerNews that uses modules. I'm sure a few did and I didn't notice.
Header files in C++ are hugely problematic. It's why the module system was designed. That system is not ready to actually be used by the vast majority of C++ programmers on the vast majority of C++ projects. Even if you happen to be using it right now in your hobby projects or even your job.
I hope someday modules are ready and make my life better. It'll be a few years at bare minimum. I'm not fully convinced C++ modules will be standard convention 10 years from now. I think there's a real chance they wither and die on the vine. I hope not! But it's definitely a possibility.
Well they are as ready for prime time, as Rust is for a large majority of C++ use cases.
Then the rest of the article is just nonsense, complaining that you have multiple ways of doing something and the like.
I sincerely hope that it turns out OK, but it's not something usable in production yet.