I worked with python java golang and c++/c and I like the attention rust has been getting and wanted to move to it from c/c++ but without sacrificing things that c/c++ offers. Since im new would anyone mind helping me out what rust does better than c++ and why/why not rust would be the right language to move (note: im a solo developer but i work with alot of low level system stuffs that requires high speed and mostly alot of mathematical calculations while being precise). If someone needs more info than the note provides, I would like a language that excels in gui development, ai model training, console applications, web servers, dll creation.
I choose Rust because it does sacrifice things C++ has whilst adding a lot that I actually need. Mostly I'm thinking about rigour and correctness here. Also Rust maintains low level capabilities, like C, all the way down to little micro-controllers and it maintains performance on a par with C whilst offering higher level abstractions.
Just like C++, Rust is a language primarily oriented towards systems programming, which makes it easy to write code with predictable performance characteristics.
It, however, adds features that proved to be useful and guarantees that proved to be necessary for serious software engineering in the 3 decades that passed between the conception of the two languages.
Contrary to some relatively popular beliefs (should I say misconceptions), Rust does not "sacrifice" anything compared to C++. The reason some C++ programmers who are new to Rust believe this is that C++ does not strictly enforce a clear structure of ownership, indirection, and exclusivity that Rust does (which is not a surprise, because practically no other mainstream language does), and consequently, most writers of C++ code are not accustomed to thinking in terms of these concepts. Some software engineers, for instance, only encounter the concept of shared-xor-mutable in seemingly very niche and specialized contexts such as database transactions, and do not necessarily make the connection to general coding practice.
Once you get the general idea, Rust is no more restrictive in terms of high-level problem solving than any other strongly and statically-typed language. The low-level mechanics of how one solves certain classes of problems changes, but there's nothing that you could do in C++ but not in Rust. It is very frequently the case that code that seems (or is) "impossible" in Rust but is (thought to be) "possible" in C++ ultimately turns out to be obviously or subtly wrong (ie., producing incorrect, unreliable, or downright undefined results), and should not have been written in C++ in the first place, either.
These days, I find it increasingly unappealing to start greenfield projects in C++. C++ is a reasonable choice if you need to interoperate with existing C++ code, but the safety and correctness benefits of Rust almost always outweigh its perceived drawbacks otherwise.
To get a good overview of some core features of Rust, with a C++ background, I can recommend watching this video presentation:
It doesn't aim to judge the two, but it just presents Rust features by contrasting to / comparing to C++ in order to make it easier and faster to present them. However, with such a comparison, I suppose (some of) the relevant differences should also become self-evident, and if you like the design choices Rust has made here, you can come to your own conclusion as a take-away in your own terms, of why Rust might or might not be 'better'.
Of you want presentations that talk more about Rust, if I recall correctly, this one is giving a good overview of points why it might be better, rather than showing too much of its actual working. Anyways, this a safe recommendation for a very good presentation to watch:
An interesting and more recent talk, which focuses on the experience (though that's experience from a company, not solo developers) with switching to Rust could be this one:
That's like saying static languages don't sacrifice things as compared to dynamically typed laguages (or vice versa).
Rust, when compared to C++, sacrifices a lot: you give up flexibility of metaprogramming, you lose the ability to use OOP, heck, you couldn't even pass generic into another generic except via crazy complicated dance with GATs. You lose many things.
Sure, you gain many important things and in, general, Rust offers better deal, but saying that you are not sacrificing things when you lose the ability to do many things that were possible in C++ is not “misconception” by any stretch of imagination.
This depends on your background very much. Some style guides (like Google's one) already teach you to write programs in a style very similar to what Rust accepts.
But the inability to just simply take one array and add one element to it, or, heck, even compile-time create latmap… that hurts.
Feature-wise Rust feel more like C++98 than C++11, but with some very important and advanced features that even C++26 doesn't have.
That's only true in a Turing tarpit sense. Strictly speaking all turning-complete languages are equivalent, but there are plenty of things that are easy in C++ but more-or-less impossible in Rust (the opposite is true, too, though Rust can do things that C++ couldn't).
Citation needed. Rust has a variety of metaprogramming capabilities (declarative and procedural macros, dyn Trait
and Any
, build scripts, the ability to embed DSLs, etc.), or close equivalents to features that the C++ community calls "metaprogramming" but aren't (generics).
No, it's true in a very pragmatic sense.
This feels like exactly the low-level detail that is different between the languages. I don't know what kind of code you are writing, but "simply take one array and add one element to it" is not something that I ever needed in a const context (assuming that is what you mean – in a dynamic, run-time context, this simply wouldn't be true because there's Vec
and other ordered contains that do possess this ability).
I'm pretty confident that if you showed me concrete code and sufficient context where "that hurts", I would be able to quickly come up with idiomatic Rust code that achieves the equivalent – likely without the necessity to transliterate a compile-time append operation.
Rust gives you the ability to write programs like you would do in Python, Java or Golang.
People who have only ever worked with high-level languages often expect predictability from their programs. If you do expect you get a compile time eror. Or runtime error. Or something predictable.
C++ is the total opposite: there are bazillion rules that you are supposed to obey and which compiler doesn't bother to check for you. And if you do mistakes then thing may work for years before change in a totally unrelated code would make your program misbehave… and often misbehave in a very strange way that's really hard to predict in advance.
Rust have sublanguage like this, too, but you don't write large parts of your code in that sublanguage (and if you do then Rust may not even be a good choice). And even if you, eventually, arrive at the need to deal with that language a lot you may still at least start with something that doesn't have mines under every bush.
And all that needs to be combined in very bizzare and strange fashion to implement things that are trivial in C++.
Try to implement function that accepts either one string or two ints and returns string with "Hello, "
prepended and sum of these two strings.
In C++ that would be this:
auto foo(auto... params) {
if constexpr (sizeof...(params) == 1) {
auto [first] = std::tuple{params...};
if constexpr (std::is_same_v<decltype(first), std::string>) {
return "Hello, " + first;
}
}
if constexpr (sizeof...(params) == 2) {
auto [first, second] = std::tuple{params...};
if constexpr (std::is_same_v<decltype(first), int> &&
std::is_same_v<decltype(second), int>) {
return first + second;
}
}
}
The need to deal with number of elements separately and with types of elements separately is a bit jarring, I've heard ZIg does better, but at least that's possible. In Rust… technically that's possible, too, but only with a lot of limitations and the combined use of macros, generics and many such things when you even try to do very simple metaprogramming.
Instead of looking of what you got and deciding what to do with these things you have to turn all logic inside out and abuse many tricks to even approach this flexibility of C++.
Please tell me you are joking. The very simple thing, present in the very first program of most tutorial println!
macros couldn't be replicated in a user's code! Just simply couldn't! Because it expands nested macros before processing their output and normal macros couldn't do that.
Please don't tell me about “lack of sacrifices” when something that you start using in your first ever program in Rust already includes magic which is not possible for “mere user”.
And when someone else asks — you just ignore the question.
Yes, I know.
Okay then.
Well, help the guy to generate these Si prefixes. Here is the C++ solution:
constexpr auto SPEC_SI = std::array{"", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"};
constexpr size_t string_size(const char *s) {
size_t len = 1;
while (*s++) {
len++;
}
return len;
}
constexpr size_t full_array_size(auto a) {
size_t len = 0;
for (auto e: *a) {
len += string_size(e);
}
return len;
}
template<auto a>
constexpr auto append_i_to_elements() {
auto fill_value = []() {
std::array<char, full_array_size(a) + std::size(*a)> strings{};
size_t out = 0;
for (const char* e : *a) {
while (*e) {
strings[out++] = *e++;
}
strings[out++] = 'i';
strings[out++] = '\0';
}
return strings;
};
static constexpr auto strings = fill_value();
auto fill_indexes = []() {
std::array<const char*, std::size(*a)> result{};
size_t index = 0;
size_t out = 0;
for (const char* e : *a) {
result[index++] = std::begin(strings) + out;
out += string_size(e) + 1;
}
return result;
};
static constexpr auto result = fill_indexes();
return result;
}
constexpr auto SPEC_IEC = append_i_to_elements<&SPEC_SI>();
It's pretty damn large and ugly (because I had to write it in isolation without any support libraries) and I definitely wouldn't use that for these 11 prefixes, but you get the idea: I regularly want to precompile things based on other things. Often these things that I want to use already exist somewhere in the program.
In C++ I can take existing language elements and produce new ones. In Rust… I can't.
The whole metaprogramming approch is inverted upside down: instead of writing code that works with some cases (that I need to process) and ignores the other cases (that I don't need to process) I'm forced to invent the grand unified theory of everything.
Something it's an acceptable sacrifice, something it's not acceptable, but please don't tell it's not sacrifice at all.
I have mentioned dynamic languages for a reason: static languages win in robustness, while dynamic languages win in flexibility.
Metaprogramming is the same: while instability in case of dynamic languages affect the end users while flexibility benefits the developer (which makes them good fro prototyping where primary user/tester and developer are one and the same, but not for much else), with metaprogramming both sides are often practicised by the same person and in such cases the need to build grand unified theory of everything is a very significant loss.
You got an answer in Generate const static strs - #2 by ZyX-I - what was wrong with that? Also, please remember you are not entitled to an answer. It's a community, not a paid consultancy.
Rust const expressions + macros may be a relatively weak point. Though I know many who feel they are a weak point in C++ too - just different problems. Zig is probably the winner there from my understanding. If that matters a lot to you, maybe present Rust isn't the best. But it also might not really matter.
A better question may be whether yours is the right approach at all in Rust. In Python they talk often about "pythonic" code - I've seen basically Java or C written in Python and it doesn't go well - the language lends to slightly different approaches to problem solving. Similarly, there is "idiomatic Go" and "idiomatic Rust". I wouldn't call different mentalities like C++ type OOP vs more functional style "sacrifices". They are simply different approaches to the same problem.
What does the code really need to do? It's easy to fall into the X/Y problem https://xyproblem.info/ - getting stuck on a detail of an implementation, when the real question should have been at a higher level. (I'm certainly guilty of it myself)
The different mindsets between languages are IMO the best reason to learn different languages. Learn Haskell and Scheme/LISP. Not necessarily to use every day, but to see different ways of approaching problems. And so I encourage everyone to learn Rust even if you don't use it - it forces you to think about memory and ownership is a way you may not have been doing consciously before. Learning Rust will help you write better C++ if that's what you decide to stick with.
That's precisely the “Turing Tarpit” answer. Instead of taking existing entity in my program and processing that into a new entity “answer” pushes me to rewrite existing code from scratch.
And we are talking about metaprogramming flexibility, isn't it? Well, from flexibility POV these two solutions are very far from equivalent.
One works when said data structures live in some third-party code while the other doesn't, for one thing.
Sure. It's all about tradeoffs. Macros in C++ are much weaker than in Rust but metaprogramming is more flexible (you can traverse and process complex types and data structures in C++, while in Rust similar facilities just simply don't exist… something like this was discussed, but AFAIK currently is not pursued). So it's not even “something Rust currently lacking but may get soon”, but pretty significant deficiency.
And I have already said that I think Rust's tradeoffs are usually better (which is obvious: why invent new language if it's worse then what we already had?).
But we are discussing this:
That's very strong assertion and if someone asserts something like this then I wouldn't expect the answer to “how can do X
to Y
” be “oh, you just need to rewrite Y
from scratch”.
Where one language pushes me to do such rewrite and the other language doesn't… well, that's pretty real sacrifice in my book.
It may be good sacrifice or bad sacrifice, but that's, most definitely, not Rust does not "sacrifice" anything.
Well, before the hoopla with JeanHeyd Meneide keynote speech happened that was supposed to be, well, a keynote. That tells me that at least some people in Rust project think it's a nice thing to have.
That, most definitely, is sacrfice, too. Again, probably justified, because OOP still doesn't have a solid theoretical foundation (and may not ever get it), but there are whole class of developers who may only think in terms of OOP.
For them it's definitely a sacrifice. I'm not one of them, for better or for worse, I was always sceptical about OOP, but I couldn't see how removal of the whole fricking development methodology and, worse, the development methodology which is used by millions of developers, can be classified as “not sacrifice”.
It's unclear how to to marry OOP (specifically implementation inheritance OOP) to with The Soundness Pledge, thus it's probably good sacrifice, but that's sacrifice, nonetheless.
The one that's used in my $DAYJOB? Marshal arguments for various APIs: GLES, OpenMAXAL, SLES, some internal ones. With C++ I juss pass pointer to the marshalling function and it traverses data structure types that I passed to, discovers what's in there, replaces callbacks with Rpc shims, etc.
All without any modifications to third-party code.
In rare cases where something really strange is encountered (like an object with vtables) qutomatic conversion fails and you need to add some specializations to the mix. Again: without touching that third-party code which lives in a different repo.
Rust's solution would be something akin to Serde, I suppose, with additional markup needed in all libraries that would be processed that way. Or may be something that would pull the required data from DWARF.
Not impossible, but much less convenient, I'm sure.
Oh, sure. I happen to know a bit of Haskell and Scheme, too. Not fan of these, but it helps to see different things in a perspective.
As I have said: I am thinking about using Rust anyway, in spite of these sacrifices that I would have to make. Because it does many other things better and, more importantly, approach that we used in C++ wouldn't work for Rust anyway because Rust, of course, couldn't just #include
C headers.
But if, instead of some C ABI libraries we would have had Rust crates and needed similar processing then the inability to handle third-party entities in a way similr to what we had in C++ would be a problem.
In some very perverse sense the problem that Rust gives us are not critical because we are not dealing with other people's Rust code yet… but that's not a viable long-term solution.
Just a heads-up: I'm the author of that question and not participating in this thread.
I've received an email from the forum, probably because the question was cited here.
What kind of ai model training?
You can do that in rust, but I think you are better off staying with python for that (jax, torch, tensorflow) - it will be faster. Jax is super fast and has good support for both gpu and cpu execution (linear algebra, gradients, etc) - which rust does not,
People often say rust is for systems programming - and it's not wrong, but note that it came out of Mozilla, and a significant % of rust programmers are javascript apostates - now they can not get the typing strict enough.
This is an underappreciated advantage of C++ which has way better support for dynamic linking, GPUs, GUIs, C compatibility, etc.
How?
How?
How?
Off the top of my head, the reasons I use rust are:
- Safety in various respects e.g. type safety
- Rust has no garbage collector, which can cause latency spikes. In languages that have a GC, by the time you notice those spikes, it's generally too late
- Rust manages not just memory but resources in general (eg files and sockets), all of it through the type and borrow systems
- Rust's type and ownership systems collude to make sound multithreading significantly simpler. As a result, the promise of Fearless Concurrency really is delivered upon
- The type system is generally sound, which cannot be said for e.g. Java
- While generally not recognized as such, technically Rust is a member of the Lisp family, as any language with fully-fledged procedural macros can be seen as such
- Contrary to languages with runtimes I can deploy my code anywhere I care to, including as part of other programs
- Rust's macro system allows for things like compile time computation
- The tooling, like Cargo, works really well and generally stays on the happy path without requiring effort on my part
- I generally don't have to squeeze my solutions into the weird molds that a lot of languages enforce, simplifying code significantly. For example, when I started using rust right after its 1.0 release in 2015, ADTs weren't even a thing in Java
- I have control over the layout of my types when desired
- I can build whatever I want in it including low level things eg embedded software or things in the realm of systems programming. Just try that in GC-collected languages, see how far you get
Is dynamic linking essential? In a world of open source updating. dependencies is only a compilation away.
Is there any reason why Rust cannot acquire good GPU support?
Is there any reason why top notch GUI's cannot be done in Rust?
What's up with C compatibility? I know there is or was a group striving for "Parity with C" to fix up any deficiencies. (Are they still going anyone?)
What is in the "etc".
Admittedly these might be good reasons to stick with C++ today. Happily don't require any of them to get what I want done.
Really. I thought the lisp family tree of languages was based on the idea of everything being a list.
That, along with the fact that Rust has syntax, is the reason a lot of people don't consider it a member of the Lisp family.
I consider it such however because the focus on the orientation around lists is a bit of a red herring. It's an implementation detail that happened to work out for decades after it was first invented. The focal point of Lisps, at least for me, is their flexibility, and a lot of that can be seen in Rust. Except enhanced with a lot of language-theoretical technology developed since then.
Procedural macros are arguably the defining feature of lisps because it allows for syntactic abstraction, something not really seen before the origin of the Lisp family, and ignored for a long time after. #BlubParadox
There are also other features that Rust inherits from Lisps, eg the concept of a closure originated there.
If wouldn't say it's “underappreciated advantage” if there are multiple teams working on trying to make it irrelevant. Crubit look really impressive from it's documentation, the only issue is that it's not designed to be used from build systems that “mere mortals” use.
But it's existence shows that people understand then need and hope to fullfill it, which far from ignoring it.
Just note that people spend a lot of hot air around GC without clearly understanding why having GC in you language is even the problem.
Ericcson uses Erlang with tracing-based GC to make telecom devices where latency is incredibly important, for crying out loud! Means tchnically presence of tracing GC shouldn't be a disaster… but when your language is built around GC people tend to ignore lifetime handling issues and design things without clearly understanding what is processed when… and when “GC spikes” are detected it's too late to fix the issue.
Yes, for some tasks, no, for some other tasks.
It's generally considred unfeasible to recompile the whole world when new version of compiler is released. Which means that without dynamic linking OS couldn't have Rust native API (but macOS/iOS/PadOS/visionIS could have Swift-native APIs).
Except in certain areas open-source is really weak. Games, e.g., are usually include lots of proprietary compunents and because of peculiarity of that field I don't think that would be changed any time soon.
I would leave discussion about whether Rust to ML-family or Lisp-family or if that's even one family or two family of languages to somehow who cares about formalisms more than me.
Rust took more from these than it took from C++, ultimately, and it took things from them because developers wanted these things, most of these things that have come from C++ were taken because they were needed, not wanted, for the target niche.
The core issue there is allocating a lot (with a GC you don't need to keep track of memory yourself, right? /s), which the object oriented model really tends to encourage. Just think about it: often there are deep inheritance hierarchies, often enough combined with some composition here and there.
Well most of those allocations generally turn into garbage pretty quickly (i.e. they don't even survive 1 generation of GC), and thus the garbage collector needs to account for that.
But unlike in Rust, garbage collected languages generally don't provide any control over when garbage is actually collected. Java for example merely provides a "hint" (I think, off the top of my head, that it's called System.gc()
), without any guarantee whatsoever w.r.t. whether or not that hint will lead to a garbage collection.
Those 2 aspects often interact rather poorly, in the sense that it tends to create large heaps of young garbage i.e. it creates a lot of work for the GC algorithm, with such latency spikes as a result. And to make it worse, OO essentially uses pointers everywhere, which makes data rather cache-unfriendly, thereby degrading performance further.
I haven't worked with Beam VM all that much, but Erlang's main paradigm isn't OO, so I strongly suspect that while of course garbage will be generated here and there, that garbage isn't nearly as voluminous as with OP languages, and thus the latency spikes aren't nearly as severe.
This would make sense when considering what Erlang/BeamVM are used for. As you mentioned, literal telephone systems have been written in it (and IIRC, WhatsApp's backend is also implemented in it). But most of that can be modeled as streaming data i.e. it should be possible to pre-allocate buffers and just keep reusing those. In that case, the garbage collector actually has very little work to do at all.