Great things about Rust that aren't just performance
197 comments
·January 12, 2025lordnacho
tsimionescu
> Better error handling. There's a few large firms that don't use exceptions in c++. New language with no legacy? Use the Ok/Error/Some/None thing.
I think this is still very much a debatable point. There are disadvantages to exceptions, mostly around code size and performance. But they are still the only error handling mechanism that anyone has found that defaults to adding enough context to errors to actually be useful (except of course in C++, because C++ doesn't like having useful constructs).
Rust error handling tends towards not adding any kind of context whatsoever to errors - if you use the default error mechanisms and no extra libraries. That is, if you have a call stack three functions deep that uses `?` for error handling, at the top level you'll only get an error value, you'll have no idea where the value originated from, or any other information about the execution path. This can be disastrous for actually debugging hard to reproduce errors.
mr_00ff00
I feel like your last point is the exact issue with exceptions, not rust’s errors. Exceptions are like having “?” on every single line.
tsimionescu
When an exception happens, you get a stack trace somewhere in your logs (unless you do something really weird). That doesn't always include all the information you'd like (for example, if the error happened in a loop, you don't get info about the loop variable).
In contrast, unless you manually add context to the error (or use a library that does something like this for you, overriding the default ? behavior), you won't get any information about where an error occurred at all.
Sure, with exceptions, you don't know statically where an exception might happen. But at runtime, you do get the exact information. So, if the error is hard to reproduce, you still have information about where exactly it occurred in those rare occasions where it happened.
n144q
> Match needs to be exhaustive.
When I see people mention C++ with MISRA rules, I just think -- why do we need all these extra rules, often checked by a separate static analysis tool and enforced manually (that comes down to audit/compliance requirement), when they make perfect sense and could be done by the compiler? Missing switch cases happens often when an enum value is modified to include one extra entry and people don't update all code that uses it. Making it mandatory at compiler level is an obvious choice.
jpc0
-Wswitch ¶
Warn whenever a switch statement has an index of enumerated type and lacks a case for one or more of the named codes of that enumeration. (The presence of a default label prevents this warning.) case labels that do not correspond to enumerators also provoke warnings when this option is used, unless the enumeration is marked with the flag_enum attribute. This warning is enabled by -Wall.
<https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#inde...>The compiler can do that... And it's included in -Wall. It's not on by default but is effectively on in any codebase where anyone cares...
Please don't argue about "but I don't need to add a flag in Rust" it's not rust, there's reasons the standard committee finds valid for why and honestly your welcome to implement your own compiler that turns it on by default just like the rust compiler which has no standard because "the compiler is the standard".
tialaramex
MISRA won't be OK with that.
MISRA requires that you explicitly write the default reject. So -Wswitch doesn't get it done even though I agree that if C had (which it did not) standardized this requirement that would get you what you need.
C also lacks Rust's non_exhaustive trait. If the person making a published Goose type says it's non-exhaustive then in their code nothing changes, all their code needs to account for all the values of type Goose as before - but everybody else using that type must accept that the author said it's non-exhaustive, so they cannot account for all values of this type except by writing a default handler.
So e.g if I publish an AmericanPublicHoliday type when Rust 1.0 ships in 2015, and I mark it non-exhaustive since by definition new holidays may be added, you can't write code to just handle each of the holidays separately, you must have a default handler. When I add Juneteenth to the type, your code is fine, that's a holiday you must handle with your default handler, which you were obliged to write.
On the other hand IPAddr, the IP address, is an ordinary exhaustive type, if you handle both IPv6Addr and IPv4Addr you've got a complete handling of IPAddr.
IshKebab
> Please don't argue about "but I don't need to add a flag in Rust"
Why not? It's a big issue. You say it's "on in any codebase where anyone cares", and I agree with that but in my experience most C++ developers don't care.
I regularly have to work with other people's C++ where they don't have -Wall -Werror. It's never an issue in Rust.
Also I don't buy that they couldn't fix this because it would be a breaking change. That's just an excuse for not bothering. They've made backwards incompatible changes in the past, e.g. removing checked exceptions, changing `auto`, changing the behaviour around operator==. They can just use the standard version, just like Rust uses Editions.
Of course they won't, because the C++ standards committee is still very much "we don't need seatbelts, just drive well like me".
n144q
> It's not on by default but is effectively on in any codebase where anyone cares...
Then why is this a MISRA rule by itself? Shouldn't it just be "every codebase must compile with -Wall or equivalent"?
SubjectToChange
>...honestly your welcome to implement your own compiler that turns it on by default just like the rust compiler which has no standard because "the compiler is the standard".
The C and C++ standards are quite minimal and whether or not an implementation is "compliant" or not is often a matter of opinion. And unlike other language standards (e.g. Java or Ada) there isn't even a basic conformance test suite for implementations to test against. Hence why Clang had to be explicitly designed for GCC compatibility, particularly for C++.
Merely having a "language standard" guarantees very little. For instance, automated theorem proving languages like Coq (Rocq now, I suppose)/Isabelle/Lean have no official language standard, but they far more defined and rigorous than C or C++ ever could be. A formal standard is a useful broker for proprietary implementations, but it has dubious value for a language centered around an open source implementation.
felipellrocha
but I don't need to add a flag in Rust
tialaramex
MISRA's rules are a real mix in three interesting senses
Firstly, in terms of what the rules require. Some MISRA rules are machine checkable. Your compiler might implement them or, more likely, a MISRA auditing tool you bought does so. Some MISRA rules need human insight in practice. Is this OK, how about that? A good code review process should be able to catch these, if the reviewers are well trained. But a final group are very vague, almost aspirational, like the documentation requirements, at their best these come down to a good engineering lead, at their worst they're completely futile.
Secondly in terms of impact, studies have shown some MISRA rules seem to have a real benefit, codebases which follow these rules have lower defect rates. Some are neutral, some are net negative, code which followed these MISRA rules had more defects.
Thirdly in terms of what they do to the resulting software. Some MISRA rules are reasonable choices in C, you might see a good programmer do this without MISRA prompting just because they thought it was a good idea. Some MISRA rules prohibit absolute insanity. Stuff like initializing a variable in one switch clause, then using it in a different clause! Syntactically legal, and obviously a bad idea, nobody actually does that so why write a whole rule to prohibit it? But then a few MISRA rules require something no reasonable C programmer would ever write, and for a good reason, but it also just doesn't really matter. Mostly this is weird style nits, like if your high school English essay was marked by a NYT copy editor and got a D minus because you called it NASCAR not Nascar. You're weird NYT, you're allowed to be weird but that's not my fault and I shouldn't get penalized.
stefan_
Because MISRA is also insane and has long bled into a middle managers dream of a style guide? It would make for a terrible language (that ironically isn't much more "secure" "safe" "reliable")
nox101
> Easy way to use libraries
This is both a blessing and a curse. Seeing the rust docs require 561 crates makes it clear that rust/cargo is headed down the same path as node/npm
Downloaded 561 crates (50.7 MB) in 5.21s (largest was `libsqlite3-sys` at 5.1 MB)
dralley
By "rust docs" you seem to mean "docs.rs, the website that hosts documentation for all crates in the Rust ecosystem", which is a little bit different than the impression you give.
It's a whole web services with crates.io webhooks to build and update new documentation every time a crates gets updated, tracks state in a database and stores data on S3, etc. Obviously if you just want to build some docs for one crate yourself you don't need any of that. The "rustdoc" command has a much smaller list of dependencies.
pornel
Cargo is 10 years old, and it's been working great. It has already proven that it's on a different path than npm.
* Rust has a strong type system, with good encapsulation and immutability by default, so the library interfaces are much less fragile than in JS. There's tooling for documenting APIs and checking SemVer compat.
* Rust takes stability more seriously than Node.js. Node makes SemVer-major releases regularly, and for a long time had awful churn from unstable C++ API.
* Cargo/crates-io has a good design, and a robust implementation. It had a chance to learn from npm's mistakes, and avoid them before they happened (e.g. it had a policy preventing left-pad from day one).
And the number of deps looks high, but it isn't what it seems. Rust projects tend split themselves into many small packages, even when they all are part of the same project written by the same people.
Cargo makes all transitive dependencies very visible. In C you depend on pre-built dynamic libraries, so you just don't see what they depend on, and what their dependencies depend on.
For example, Rust's reqwest shows up as 150 transitive dependencies, but it has fewer supported protocols, fewer features, and less code overall than a 1 dep of libcurl.
zdragnar
Almost all of the things that were wrong with NPM were self inflicted. No name spacing packages by default, allowing packages to be deleted / removed without approval, specifying install ranges and poor lock file implementation and so on.
There's an argument to be made that there are too many packages from too many authors to trust everything. I don't find the argument to be too convincing, because we can play what-if games all day long, and if you don't want to use them, you get to write your own.
skydhash
The issue is micro-packages. Instead of a few layers between the os and your code, you find yourself with a wide dependency tree, with so many projects that it’s impossible to audit.
spullara
this is good actually
jvanderbot
Add to this: trait system vs deep OOP.
Really nice macro system.
First class serde.
First class sync/send
Derives!
Asraelite
> First class serde.
What do you mean? `Serialize` and `Deserialize` are not part of std.
tialaramex
It's true, they're not part of the standard library. Nevertheless, it is conventional to provide implementations for things you reasonably expect your users might want to serialize and deserialize. Standard guidance includes telling you to name a feature flag (if you want one for this) serde and not something else so as to reduce extra work for your users.
Because Rust's package ecosystem is more robust it's less anxious about the strict line between things everybody must have (in the standard library) and things most people want (maybe or maybe not in the standard library). In C++ there's a powerful urge to land everything you might need in the stdlib, so that it's available.
For example the FreeBSD base system includes C++. They're not keen on adding to their base system, so for example they seem disinclined to take Rust, but when each C++ ISO standard bolts in whatever new random nonsense well that's part of C++ so it's in the base system for free. Weird data structure a game dev wants? An entire linear algebra system from Fortran? Comprehensive SI unit systems? It's not up to the FreeBSD gatekeepers, a WG21 vote gets all of those huge requirements into FreeBSD anyway.
pjmlp
"Applying Traits to the Smalltalk Collection Classes", 2003
https://rmod-files.lille.inria.fr/Team/Texts/Papers/Blac03a-...
Traits as CS concept, are part of OOP paradigm.
rmgk
Traits in Rust are more a variant of Haskell typeclasses than of Smalltalk traits.
The whole FP vs OOP distinction does make little sense these days, as it has mostly been shown that each concept from the one can neatly fit within the other and vice versa.
sshine
Traits as CS concept, are part of FP paradigm.
Reverse Uno!
kccqzy
The traits concept mentioned in your link looks very different from Rust traits. It describes something more akin to Java interfaces.
jvanderbot
There are shades of OOP, and while you're technically correct I think the meaning of my post is clear.
mananaysiempre
> Match needs to be exhaustive. When you add something to the enum you were matching, it chokes. This is good.
There’s a reason why ML and Haskell compilers generally have that as a warning by default and not an error: when you need a pipeline of small transformations of very similar languages, the easiest way to go is usually declare one tree type that’s the union of all of them, then ignore the impossible cases at each stage. This takes the problem entirely out of the type system, true, but an ergonomic alternative for that hasn’t been invented, as far as I know. Well, aside from the micropass framework in Scheme, I guess, but that requires exactly the kind of rich macros that Rust goes out of its way to make ugly. (There have been other attempts in the Haskell world, like SYB, but I haven’t seen one that wouldn’t be awkward.)
Animats
> Rust is basically a bag of sensible choices.
Mostly yes. In C/C++, the defaults are usually in the less safe direction for historical reasons.
tialaramex
It's not about less safe, the C++ defaults are usually just wrong. It's so well known that Phil Nash had to make clear whether he was giving the same talk about how all the defaults are wrong at CppCon or a different talk, otherwise who knows.
For some cases you can make an argument that the right default would have been safer. For mutability, for avoiding deductions, these are both sometimes footguns. But in other cases the right default isn't so much safer as just plain better, the single argument constructors should default to explicit for example, all the functions which qualify as constexpr might as well be constexpr by default, there's no benefit remaining for the contrary.
My favourite wrong default is the memory ordering. The default memory ordering in C++ is Sequentially Consistent. This default doesn't seem obviously wrong, what would have been better? Surely we don't want Relaxed? And we can't always mean Release, or Acquire, and in some cases the combination Acquire-Release means nothing, so that's bad too. Thus, how can Sequentially Consistent be the wrong default? Easy - having a default was wrong. All the options were a mistake, the moment the committee voted they'd already fucked up.
dataflow
> Move by default. If you came from c++, I think this makes a lot of sense.
> Immutable by default.
In C++, these two fight each other. You can't (for the most part) move from something that's immutable.
How does Rust handle this? I assume it drops immutability upon the move, and that doesn't affect optimizations because the variable is unused thereafter?
NobodyNada
In Rust, when you move out of a variable, that variable is now effectively out-of-scope; trying to access it will result in a compile error.
Mutability in Rust is an attribute of a location; not a value, so you can indeed move a value from an immutable location into a mutable one, thus "dropping immutability". (But you can only move out of a location that you have exclusive access to -- you can't move out of an & reference, for example -- so the effect is purely local.)
dataflow
Yeah that sounds about like what I expected. Thanks!
remram
You can't refer to any old location so there is no observable mutation. For example you can't move if a reference exists.
lightingthedark
Rust moves aren't quite the same as C++ moves, you can think of them more like a memcpy where the destructor (if there is one) doesn't get run on the original location. This means you can move an immutable object, the object itself doesn't have to do anything to be moved.
baq
Not 100% sure but sounds like you want Pin<>?
OJFord
> Testing is part of the code, doesn't seem tacked on like it does in c++.
Or most languages! Many could easily imitate it too. I'd love a pytest mode or similar framework for python that looked for doc tests and has a 'ModTest' or something class.
ninkendo
> I can express a lot in Python, but I don't trust the code as much without robust tests.
This is a major part of why I like languages like rust. I can do some pretty fearless refactoring that looks something like:
- Oh hey, there’s a string in this struct but I really need an enum of 3 possible values. Lemme just make that enum and change the field.
- Cargo tells me it broke call sites in these 5 places. This is now my todo list.
- At each of the 5 places, figure out what the appropriate value is for the enum and send that instead of the string.
- Oh, one of those places needs more context to know what to send, so I’ll add another parameter to the function params
- That broke 3 other places. That’s now my to-do list.
Repeat until it compiles, and 99.9% of the time you’re done.
With non-statically-typed languages you’re on your own trying to find the todo list above. If you have 100% test coverage you may be okay but even then it may miss edge cases that the type checker gets right away. Oh and even then, it’s likely that your 100% test coverage is spent writing a ton of useless tests that a type checker would give you automatically.
As nice as weakly/dynamically typed languages are to prototype greenfield code in, they lose very quickly once you have to maintain/refactor.
Cyph0n
And if say one of your enum variants expects a string reference (pointer), the borrow checker will guide you through ensuring that the reference you pass in is valid at all callsites.
Importantly, no tests are required to guarantee that the refactor is safe - although no guarantees that it’s logically correct.
On the other hand, doing this exercise in a different low-level language involves a lot more “thinking” instead of just following the compiler’s complaints :)
NetOpWibby
I feel this, but with Typescript (coming from jQuery and then ES4/5). I love how it forces you to code well.
You can change your tsconfig to ignore the strictness but I don’t.
lblume
By default, strictness is opt-in with TypeScript, and many JS APIs, especially older ones, don't even have types yet.
Having a type system from the start that cannot be disabled and that forces you to always think of types instead of allowing sprinkling 'as any' when the code works but doesn't compile which is a major annoyance, is a huge benefit in my opinion.
augusto-moura
All JS APIs are typed (browser, nodejs, etc.), if you meant libraries, yes, not all of them are typed. But the vast majority have community types in DefinitelyTyped. Also, it is trivial to type an unknown library yourself, or at least type only the relevant parts for your work.
zachrip
> and many JS APIs, especially older ones, don't even have types yet.
This is pretty much not the case these days, the packages people use mostly have types.
Aeolos
With rust, I treat compiler errors as ultra-fast unit tests. I share the same experience: once it compiles, 99.9% it works fine on first try. It's a wonderful development experience.
lblume
I completely agree, if it is really 100% Rust, or some great high-level bindings. Else it just becomes C++ with nicer syntax imo, and if my code isn't anything too fancy I could just write it in Python which likely has even more ergonomic bindings.
In my free time I code 90+% in Rust, but for some areas, like OR (SAT, MILP, CSP), ML or CAS Python seems to be the better choice because types don't matter too much and if your code works, it works.
humanrebar
For what it's worth, I get the same experience you're describing with statically typed python wired up to mypy. Not that rust and python have the same feature set in other ways.
perfmode
Where can I learn more about statically typed python?
timeon
> - Oh hey, there’s a string in this struct but I really need an enum of 3 possible values. Lemme just make that enum and change the field. ...
Heh I just did this today. Rust is really good language to prototype and refactor in.
jimbob45
With Visual Studio and C#, you can do that with a built-in wizard without even having to compile.
It scares me how good C# is these days. Every killer feature of Rust and Lisp is already in C# or started there. Visual Studio makes VSCode look like a 90s shareware tool. Even the governance, by MS of all entities, is somehow less controversial than Rust’s.
tialaramex
It's really not true that "every killer feature of Rust and Lisp is already in C#" although it's certainly true that C# is a nicer language today than it was twenty years ago.
Sum types are a must-have for me. I don't want to write software without sum types. In C# you can add third party libraries to mostly simulate sum types, or you can choose a style where you avoid some of the worst pitfalls from only having product types and a simple enumeration, but either is a poor shadow to Rust having them as a core language feature.
Also VS is a sprawling beast, I spend almost as much time in the search function of Visual Studio finding where a solution I've seen lives as I do hand solving a similar problem in Vim. I spend the time because in Vim the editor won't get in my way when I solve it by hand, while VS absolutely might "helpfully" insert unrelated nonsense as I type if I don't use the "proper" tools buried in page 4 of tab 6 of a panel of the Option->Config->Preferences->Options->More Options->Other section or whatever.
Visual Studio is what would happen if Microsoft asked 250 developers each for their best idea for a new VS feature and then did it, every year for the past several decades, without fail. No need for these features to work together or make sense as a coherent whole, they're new features so therefore the whole package is better, right? It's like a metaphor for bad engineering practice for every Windows programmer to see.
neonsunset
Use VSC instead. The higher-level alternative to Rust is more so F# than C# as it has comparably powerful type system with different tradeoffs (gradual typing and full type inference across function boundaries - it's less verbose to write than Rust because of this). Otherwise C# is not tied to VS at all because all the tools that it provides have alternatives either in Rider/JB suite and/or in a self-contained CLI way together with just using VS Code. .NET's CLI is very similar to Cargo in either case.
gfna
c# has been my go to language for everything except frontends for the past 15 years, but there are still some things I really miss from Rust. The top one is probably pattern matching. Sure C# has something similar with switch expressions, but with them you must assign something, and they cannot contain code blocks. Related to this something like enum variants is also missing, and therefore making something similar to Result or Option is not really feasible without it being quite hacky. Also being able to create new types from existing ones with eg struct Years(i64); and pass it around typed is quite nice in Rust (F# has something similar, however there it will then always also be assignable to i64, so is not very helpful for catching incorrect usage.
arwhatever
I’d be eager to hear folks poke holes in this opinion, but it seems like C# has made a wild mess of class/record initialization in recent versions, whereas in Rust fields are either simply required or are optional.
neonsunset
> Also being able to create new types from existing ones with eg struct Years(i64); and pass it around typed is quite nice in Rust (F# has something similar, however there it will then always also be assignable to i64, so is not very helpful for catching incorrect usage.
F# has units of measure which are quite a bit more powerful: https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...
macagain
Visual Studio ONLY works well on Windows, the mac version is not the same, and certain features just does NOT work as well as the Windows version. And having a language tight down to an IDE which is tight down to a proprietary OS is a deal breaker for me! No matter how good it is these days with every killer feature of Rust and Lisp.
I would rather use a more raw unrefined version of tech that is open source. So my code and DX is not at wimp of some corporate over lord! And given MS's track record I do
neonsunset
C# with Rider is a more comprehensive experience than Rust + RA + VSC. C# in VSC is about on the same level with Rust regardless of the platform.
I always considered C#, F# and Rust as languages complementary to each other since each has their own distinct domain and use cases despite a good degree of overlap. Much less so than Java/Kotlin and Golang or any interpreted language (except Python and JS/TS in front-end) which are made obsolete by using the first three.
dragonwriter
> This is a major part of why I like languages like rust. I can do some pretty fearless refactoring that looks something like:
The process you describe (with “compile” replaced with “typecheck”) works fine for me in Python, with Pylance (and/or mypy) in VSCode.
> With non-statically-typed languages you’re on your own trying to find the todo list above.
This would be more accurately “in workflows without typechecking”, it’s not really about language features except that, long ago, it was uncommon for languages where running the code didn't rely on a compilation step that made use of type information to have typechecking tools available for use in development environments, and lots of people seem to be stuck in viewpoints anchored in that past.
ninkendo
I don’t see a need to get so hung up on the nomenclature… it should be obvious that if you’re using a separate type checker in python, then of course you’ll also get the benefits I describe. The distinction I’m drawing is of course “has type checking” vs “does not hand type checking”, and it seems like you simply agree with me.
The problem with python, ruby, JavaScript, and similar languages is that while yes, they have optional type checkers you can use… they were invented after the fact, and not everyone uses them, and it’s not mandatory to use them. The library you want to use may not have type information, etc. It’s a world of difference when the language has it mandatory from the start.
And that’s not even getting into how (a) damned good rust’s type checker is (b) the borrow checker, which makes the whole check process at least twice as valuable as type checking alone.
mardifoufs
I think that's still a different process. I really love pylance, but the issue is that while it can make your code almost as good as it can be with a compiled (statically typed) language if you use strict typechecking, it still can't make up for the issues that come with any library you use. Some popular packages are well annotated but some aren't, meaning that it's just not as good as soon as you start using 3rd party packages.
the__alchemist
The pros/cons of rust add up better than other languages. The people who I hear (recently: Jon Blow) throw spears are usually correct, but what they're missing is that you could throw pointier ones at the alternatives. Some examples:
- Best mutability ergonomics of any language. E.g. `&mut` in a function parameter means the funciton can mutate it; `&` means it can't. This might be my favorite part of rust, despite sounding obvious. Few languages have equivalents. (C++ and D are exceptions).
- Easy building and dependency management
- No header files
- Best error messages of any language (This is addressed explicitly in the article)
- Struct + Enums together are a fantastic baseline for refactorable, self-consistent code.
- As fast as any
- Great overall syntax tradeoffs. There are things I don't like (e.g. Having to manually put Clone, Copy, and PartialEq on each simple enum, and having to manually write `Default` if I need a custom impl on one field), but it overall is better than alternatives.
Rust enthusiasts online are often unpleasant, and it's perhaps their fault people are put off by the language. They repeat things like "fearless concurrency" "if it compiles, it works", and "that code is unsafe/unsound" without critically thinking. Or, they overstate rust as a memory-safety one-trick, while ignoring the overall language advantages.Tangent: Async rust is not my cup of tea for ergonomics and compatibility reasons. I have reason to believe that many people who like it think Async is synonymous with concurrent processes and nonblocking code.
huijzer
> Rust enthusiasts online are often unpleasant, and it's perhaps their fault people are put off by the language.
I guess this is also a bit in the eyes of the beholder. It seems that any group that is enthusiastic about something new is “unpleasant” nowadays.
iknowstuff
I like async rust because its an engineering marvel. There are other ways to do it, but they are all worse, slower, and less versatile: https://embassy.dev/
wavemode
> There are other ways to do it, but they are all worse, slower, and less versatile
I would disagree with this, personally. Due to being tacked onto the language after the fact, the design of Rust's async made a number of concessions in order to fit into the language (for example, it had to work around the pre-existing restriction that all types are moveable by default).
But you're correct that no current popular language has yet developed anything better.
null
the__alchemist
I do lots of embedded; not an Embassy fan for the classic Async reasons. Disagree on being worse, slower, and less versatile. That is wrong on slower; the other two traits are subjective. The embassy creator is a great programmer, and we see eye-to-eye on typestates and HAL unification, but not async.
Thorrez
>Few languages have equivalents. (C++ and D are exceptions).
I would say Rust is still better than C++ here, because in Rust const is default. In C++, people often either forget to write const, or intentionally don't write const because writing it everywhere clutters up the code.
berkut
> or intentionally don't write const because writing it everywhere clutters up the code
I don't often like being judgemental (at least publicly!), but I'd argue that's just people being very bad developers...
You could argue having to add '&mut' at call sites everywhere (i.e. opposite to the way C++ does const in terms of call site vs target site) also clutters up the code in terms of how verbose it is, but it's still largely a good thing.
tonyedgecombe
>Rust enthusiasts online are often unpleasant
The Ruby community seems the nicest, I wonder why that is.
adityajha36
Coming from a Python background, working with data heavy analytics in finance, one of my biggest Rust-is-the-one moment has been because of Polars. It lets me do everything I could do with Pandas, but with added speed and safety. Lack of such packages (as far as I'm aware) in Java, C++ and other languages keeps an entire ecosystem of data-heavy workloads in Python. But most Python projects I've found are very hard to scale beyond the prototyping phases.
EddieRingle
Are you familiar with Kotlin's DataFrame? (https://github.com/Kotlin/dataframe)
JetBrains also keeps a list of other data analysis libraries for Kotlin (and Java) as well: https://kotlinlang.org/docs/data-analysis-libraries.html
8s2ngy
Even though I am not as proficient with Rust as I am with the languages I use at work, it has quickly become my favorite language as I dive deeper into it. It has helped me connect many dots that were fuzzy in my mind, like static typing, enums, pattern matching, traits, compile-time safety checks, and so on. A fair argument can be made that these are not unique to Rust, but it presents them in a cohesive package that I have not seen in any other language. Add to that its well-integrated build system and the enthusiastic community of developers who are passionate about it, and its appeal is undeniable. The lessons I gain from Rust directly translate into a better way of reasoning about code written in other languages.
IX-103
One thing I don't like about Rust is implicit function return. Flow control should always be explicit. Given the other choices made in Rust to make flow control explicit (such as the absence of exceptions), I was surprised to find this choice.
Fortunately the "return" keyword is allowed so I include it in my code to make it explicit. I just have to remember to look for it in any other code I'm reviewing.
tonyedgecombe
I don't think match would work so well if blocks weren't expressions.
mhsdef
Nails it. I'm so tired fighting incidental complexity--our tools.
Speed is great but let me just focus on the business problems and write something durable.
sesm
I'll swallow the bait and try asking some sceptical questions here.
My understanding of Rust memory management is that move semantics and default lifetime-checked pointers are used for single threaded code, but for multi-threaded code Rust uses smart pointers like C++, roughly Arc = shared_ptr, Weak = weak_ptr, Box = unique_ptr.
My question is: what extra static checks Arc has over shared_ptr? Same for Weak over weak_ptr, and Box over unique_ptr.
koito17
Here's a static check Rust's Box<T> offers over C++'s std::unique_ptr<T>.
The following program is obviously incorrect to someone familiar with smart pointers. The code compiles without error, and the program crashes as expected.
% cat demo.cpp
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<std::string> foo = std::make_unique<std::string>("bar");
std::unique_ptr<std::string> bar = std::move(foo);
std::cout << *foo << *bar << std::endl;
}
% clang -std=c++2b -lstdc++ -Weverything demo.cpp
warning: include location '/usr/local/include' is unsafe for cross-compilation [-Wpoison-system-directories]
1 warning generated.
% ./a.out
zsh: segmentation fault ./a.out
The equivalent Rust code fails to compile. % cat demo.rs
fn main() {
let foo = Box::new("bar");
let bar = foo;
println!("{foo} {bar}")
}
% rustc demo.rs
error[E0382]: borrow of moved value: `foo`
--> demo.rs:5:13
|
2 | let foo = Box::new("bar");
| --- move occurs because `foo` has type `Box<&str>`, which does not implement the `Copy` trait
3 | let bar = foo;
| --- value moved here
4 |
5 | println!("{foo} {bar}")
| ^^^^^ value borrowed here after move
help: consider cloning the value if the performance cost is acceptable
|
3 | let bar = foo.clone();
| ++++++++
Not only does Rust emit an error, but it even suggests a fix for the error.sesm
That's a very good example, thank you!
calo_star
> but for multi-threaded code Rust uses smart pointers like C++
That's not the whole story. There's also Send and Sync marker traits, move by default semantic also makes RAII constructs like Mutex<T> less error prone to use.
ninkendo
The big deal is that Rust will refuse to compile if you forget to use the appropriate smart pointer. You can’t just accidentally forget that you left a mutable reference in another thread: if you want a thread to use a reference, Rust will ensure you don’t accidentally let another thread access it simultaneously.
CJefferson
In rust, you still can’t get mutable access to any object in two threads at the same time in a non-thread safe way.
In “very rough c++ish”, stuff in a shared ptr is immutable unless it is also protected by a mutex.
sesm
Ok, so do I understand correctly that Arc<MyType> would be read-only, and for write access I'll have to use Arc<Mutex<MyType>> or Arc<RwLock<MyType>>? So what about Mutex and RwLock, do they have any static checks associated with them? Do they introduce an extra layer of pointer indirection, or Rust resolves Arc<Mutex> and Arc<RwLock> to 2 different implementations with only 1 layer of indirection?
remram
The extra static checks are the mutable vs non-mutable references, and the Sync/Send traits. Mutex does not introduce indirection.
ausbah
without even reading the article hands down the expressiveness of algebraic data types and pattern matching it enables is my absolute favorite part of rust
isodev
Wholeheartedly agree. One can just be at ease that the compiler has it covered and one can focus on coding. Both the tooling and the syntax for these protections is kind of “out of the way” as well - I don’t have to learn extra keywords just to convince the compiler that what I’m doing is “safe”, it can do that by itself.
jurgenkesker
I enjoy Rust, but have settled on Kotlin for my language of joy. I use it in my day job for Android, but also recently started converting personal project backends and APIs to it (mainly from Ruby). I really like the ease and joy Kotlin gives me, and if I need a very high performance or very low level project/library, I'll write it in Rust. Rust is too slow for me (when coding), so a bit too low level I guess. In Kotlin I can express everything I want.
8s2ngy
Two things concern me about Kotlin:
1. The language and its future are heavily intertwined with JetBrains and their motivations. It's difficult to say whether this is a good thing or bad, but issues like the one discussed at https://discuss.kotlinlang.org/t/any-plan-for-supporting-lan... don't inspire confidence.
2. Java seems to be moving ahead at a rapid pace and is slowly absorbing many of the features that once distinguished Kotlin. This makes it difficult to jusify introducin Kotlin at a company where Java is heavily used.
yodsanklai
> I enjoy Rust, but have settled on Kotlin for my language of joy.
Isn't it comparing apples and oranges? Is there any good reason to use Rust if you can live with a GC?
hooli_gan
Yeah, like startup time and (cross-)compiling to a static binary. Although the compile time can get annoying.
akkad33
What feature do you use Kotlin for that does not exist in modern Java (21+)?
lblume
Complete by-default null safety is a big point, extension functions are just nice, smart casts, proper data classes and operator overloading, and simple expressive functional stuff like range syntax and reified generic types for inline functions. In general the Kotlin language feels more usable, yet less bloated (wrt the actual code, not the features ofc).
pryelluw
I’ve been focusing more and more on rust for my personal projects and I agree with all of these. What im waiting for is the Django equivalent in rust. Dunno if its already here but I’m hoping.
The one thing I do enjoy in rust is how you don’t need an excessive amount of tests to ensure it runs fairly correctly. I’ve spend more time writing tests in the last ten years using Python/js than writing actual code. Such a waste of productivity.
ku1ik
I would argue web dev is just not Rust’s sweet spot, and I wouldn’t hold my breath for „Django for Rust”.
pryelluw
That’s what people said about Python before Django. They asked why not use PHP or Perl. But Django came along and established a strong set of patterns that make web dev much easier these days.
vkazanov
As somebody who wrote maybe half a million lines of python code, I still must say that it is Rails that became a model.
Django was (is?) the default tool for python, but i don't remember it being discussed as widely.
the__alchemist
Concur. I don't do web backends in rust for this reason. There are some good Flask analogs though.
pryelluw
Mind sharing what libraries you use ?
Aeolos
Not the same poster, but I am using Axum (webserver) + Maud (templating engine) + SeaORM (db adapter) + HTMX and I'm finding the end-result highly productive.
Even though Rust is more verbose, and SeaORM has a few quirks, I am making faster progress in Rust than my existing mature Typescript + Node + apollo-graphql + ReactJS setup. Once I was over the initial setup & learning curve (about a week), I find myself able to spend more time on business logic and less time hunting runtime bugs and random test failures. There's something almost magical about being able to refactor code and getting it up and running in a matter of minutes/hours, compared to days for similar operations in Typescript.
It's definitely still a young ecosystem that desperately needs a Django equivalent (loco.rs is worth keeping an eye out, but it's not there yet.) But I'm willing to tackle a bit of immaturity & contribute upstream to avoid the constant needless churn of the js world.
the__alchemist
For web? Django.
For me, there's a headline draw, which is the borrow checker. Really great.
But apart from that, Rust is basically a bag of sensible choices. Big and small stuff:
- Match needs to be exhaustive. When you add something to the enum you were matching, it chokes. This is good.
- Move by default. If you came from c++, I think this makes a lot of sense. If you have a new language, don't bring the baggage.
- Easy way to use libraries. For now it hasn't splintered into several ways to build yet, I think most people still use cargo. But cargo also seems to work nicely, and it means you don't spend a couple of days learning cmake.
- Better error handling. There's a few large firms that don't use exceptions in c++. New language with no legacy? Use the Ok/Error/Some/None thing.
- Immutable by default. It's better to have everything locked down and have to explicitly allow mutation than just have everything mutable. You pay every time you forget to write mut, but that's pretty minor.
- Testing is part of the code, doesn't seem tacked on like it does in c++.