I find rust quite elegant but also quite complex. There's a scale for computer languages between a compiler that doesn't do enough checking (JavaScript, Python), and ones that do too much where you spend a lot of energy trying to satisfy the compiler. I think rust falls a bit too far to this latter side for my taste. I like the language but I feel more productive with Go even although Go isn't as elegant in my mind and has some flaws (nil, no generics).
I think rust is nicer for where you would previously use C or C++ but I don't work in that domain.
I would never write a ray tracer in Go, although it could be done.
> A lot of functional languages offer this by avoiding mutable data, but that's high cost
This is true, but the inverse is that Rust's ownership model itself poses its own high cost in terms of a significant cognitive burden (and while I have no doubt that the burden decreases with time, I'm very skeptical that it ever gets to the point of a GC language). This is roughly what you're saying in the second paragraph, but I wanted to be a bit more explicit that the ownership model itself comes with a cost.
My experience in this regard: Once you stop thinking in OOP, and embrace Data Oriented Design (which really helps structure your program like Rust wants it to be to avoid lifetimes issues) and get proficient with type system and popular libraries, Rust is far more productive and effortless than mainstream GC languages. I routinely do non-trivial changes to my open source projects late in the evening, after long day of work, after I've finally put my kids to sleep, and despite sloppiness and lethargy it's very much effortless.
I don't have to debug weird stuff. Compiler just straight tells me what I did wrong and how to fix it. Languages expressiveness is first class, so I don't have think too much what's the best way to express this or that idea. Then - not only I don't have to worry about `free`, but I also get deterministic destruction: files, sockets and any other "resources" get closed/freed when I'm done with them. And then the libraries... they have beautiful, well-documented, and hard to misuse APIs.
Rust is a magnificent language for many things, but I'm manifold more productive in Go or Python, even in concurrent contexts. Notably, the ownership rules guarantee correctness, but they also prohibit all sorts of correct programs (e.g., functions that only run in a single threaded context or whose shared data doesn't actually mutate). I would say I have the hang of these rules, but I still struggle to make sense of the lifetime error messages (and I find this nearly impossible when I'm tired), contrary to your experience about the clarity of the compiler messaging. I don't mean to slight the compiler--communicating about lifetimes is fundamentally hard; however, the point is that these error messages must be dealt with in all code even though the overwhelming majority of my code isn't subject to race conditions in the first place.
I really want to like Rust (I love it in theory); however, frankly I don't see how Rust can approach a GC language with respect for productivity for anyone unless the domain prioritizes correctness and/or performance above all else.
My domain is games, and while this usually prioritizes performance that's not really what I care about (so I could be using Python for instance and it would be 'ok'). My take is this: When it comes to productivity even if writing the same code is easier in Python than Rust, working over time in a Python project (or any dynamic language really) is just a nightmare for me. No matter how many tests I write or how I structure my code when I have to add a new feature that touches large parts of the existing system I just have 0 confidence it is correct. I have to run my code through all the branches to make sure I didn't forget to, idk, add the new parameter to all function calls or something.
Rust on the hand is just reliable. I can start updating my code with a half baked idea, start adding types to things, removing parameters from functions, changing variables names and I KNOW nothing will sneak on me during runtime. This is more of a feature of strong type systems than Rust itself, but still this is what productivity means to me.
(Also, to me lifetimes were the hardest concept to grasp, but I think once you really get what they mean that's when the language clicks and you get the same productivity you would elsewhere)
In practice the most important things for general productivity are always: what is your team familiar with, what are you familiar with, and are the types of libraries that you need available. Quite often these are skewed in Go's favor.
Coming from JavaScript, my Rust code is often almost identical to my JS code. With perhaps a few extra `.clone()`s, a bit of extra error handling code (which is of course the benefit!), and type annotations.
In "burdensome" situations, you can always defer ownership checking to runtime with the Rc<RefCell<T>> and Arc<Mutex<T>> patterns. It's less performant than using pure compile-time checks, but not nearly as problematic as general GC, or immutable-only data structures.
I think I prefer a GC most of the time, and when that's not true I'm fine if the language offers me an escape hatch (unsafe in Go, paired with a native allocator like jemalloc.)
For me, it does impose a (small) cost over a GC language. But other aspects of the language (enums, traits, type inference, etc) are so much better than most mainstream languages that it more than makes up for this. I write JavaScript quicker than I write Rust, but I write Rust quicker than I write Java.
Available in any ML derived language, with the GC productivity.
Straight C seems to be used for transparency and compiler compatibility though, which could be at odds with rust's current workflow.
I think pragmatically using rust, modern C++ or even C, any concurrency needs to be done at a generic and separate architectural level that is not part of the day to day refinements and features of a program. I don't think constantly thinking about data races, low level synchronization, or even lifetimes more complex than being returned from the creation scope is ever going to be a recipe for long term success.
This is all to say that I think rust's safety strengths are greatest in areas of a program that should actually be as small as possible and changed rarely.
I almost never use shared_ptr, since I know the lifetimes of the memory I use. I know the lifetimes because I keep almost all actual memory management within data structures made of mostly STL data structures. The custom data structures don't need destructors because vector<> will take care of it etc. I never use inheritance and never use polymorphism. The core logic then just uses these compound data structures made from STL containers and the lifetimes are frequently trivial.
Yes. Originally Rust seemed to be the answer to the three big questions in C: "How big is it", "Who owns it", and "Who locks it". Those are the cause of most of the vulnerabilities you see in CERT advisories. Rust finally had a working, efficient solution to all those problems. A huge breakthrough.
Then the type-theory people, the "metaprogramming" people, and the functional people got in there and mucked up a perfectly good imperative language. They turned writing code into either puzzle-solving or copying forms you don't really understand.
That already happened to C++. The "metaprogramming" people complicated templates to the point that "you are not supposed to understand this" is now normal for some standard templates. Now the C++ crowd is trying to emulate Rust features like move semantics without a borrow checker, which doesn't really work.
Go was a reaction to that. Go is a mediocre language, but it's good enough for most web back-end stuff, which is why Google developed it.
Clang tidy and Visual C++ lifetime profile are that borrow checker.
If they get good enough results, GCC and other commercial compilers will definitely adopt them as well.
For example, I was quite surprised that with all the security sales story, Azure Sphere SDK is C only, which basically torpedoes their sales pitch.
At least MSR seems to keep having a go at Checked C.
Before Rust, I was close to saying that thread-based parallelization was almost impossible to do safely, and was moving to only doing process-level parallelization.
Do not confuse data races (a subset of race conditions that many languages solve) with race conditions (a way harder problem that there is no general solution to).
> A trivial counterexample is the protection against data races.
Neither that, memory safety, nor forcing you to deal with optionality is unique to Rust.
The big difference is that Rust manages to do these things while also having similar performance characteristics as C/C++. The trade-off is that the programmer has to explicitly deal with these issues.
I personally haven't yet seen a safety language feature of Rust that is not somehow available in other languages with similar claims. I would appreciate learning otherwise if I'm missing something.
I think the only feature that would push Rust to being at the forefront would be "const generics" [1] or true dependent types down the line.
[0] https://en.wikipedia.org/wiki/Software_transactional_memory
Rust is the perfect option for projects that:
1) are exposed to untrusted data
2) and, at the same time, their performance is critical
If one of those isn’t true, there are better options. If they are both true though, Rust is pretty much unique.
Rust and Haskell and standard C and C++ are all non-starters.
The long version is that you could use 'set!' with Vars. But this is very much an avoided practice. Another caveat is that Clojure is hosted (primarily JVM and JS), so any code you call via interop has the characteristics of the host target.
But idiomatic Clojure uses at least Atoms or Refs and even those are discouraged for anything else than storing state, all the algorithmic (transformation, branching and so on) code follows the FP paradigm.
A Rust analogy is the 'unsafe' escape hatch. You can use it but it would be unidiomatic, inconvenient and in most cases unnecessary.
tl;dr: no
Both using 'unsafe' and using String instead of an Enum would compile without runtime guarantees. But such code would stick out.
These markers are irrelevant in a single-threaded scenario.
I’ve googled everywhere but I can’t find a single source of information that completely explains this, possibly showing realistic source code and elaborating the way it could generate memory errors or be miscompiled in case of multiple mutable references.
The TL;DR is: iterator invalidation can happen even without threads.
Another example is if someone tried to use a Vec x inside the closure passed to x.drain.
> doesn’t Rust refuse to compile even when you use non-concurrency safe objects in a single thread?
and given that most types (Vec, HashMap, etc.) aren't "concurrency-safe" (thread-safe?), your answer
> yes
is rather perplexing.
Any language that relies on GC and avoids mutation e.g. by extrmely inefficient data structures (persistent data structures and other immutable data) obviously achieve mostly the same level of safety but at a huge cost.
Unsafe code blocks as opt-in in systems languages appeared for the first time in 1961, followed by multiple variations of it since then, far from being a novelty by now.
One might argue that “well those small parts of a rust program are as unsafe as the entire C# program” but that would be a completely nonsensical argument so I’m going to give you the benefit of the doubt that’s not what you meant.
Besides, the recent security exploits have proven that it is time to go back into multi-processes, and here Rust doesn't have anything to offer.
So one should tune a bit down the tone of labelling all the other languages as unsafe, even managed ones, industry standards like SPARK, or system research languages like ATS.
As someone who's worked more with JS/ts/java, I find the borrowing system's guarantees far more interesting wrt concurrency. Yet that's often little more than a footnote in long articles about how rust is safer than c++.
Rust gets compared with C++ because that is the target audience. If you don’t need the properties of a systems programming language, you are better off avoiding both C++ and Rust for increased productivity.
Can someone share a good, authoritative source that explains the variable passing semantics of Rust in terms that a C/C++/Java programmer can understand?
You seem to think you have less control over your allocations than in other languages, but this is not accurate, particularly contrasted to GCd languages.
How can I definitively tell which of the two things will happen? Under what conditions will it choose to make a copy instead of passing the address?
The idea is to detect large moves and do a pass by reference, much like Swift does. On my personal roadmap I have detecting these cases and lint against it, suggesting passing the argument as & or &mut as appropriate. That way the performance implications and tradeoffs are visible in the code you write/read.
On one level, Rust has no defined ABI, so on some level, the answer to this question is "no way to tell."
On another level, Rust doesn't have pass by reference. Like C, everything is pass by value, and pointers are themselves values, sometimes called "pass by value by reference." So semantically, if you pass a value, a value will be passed, and if you pass a reference, a reference will be passed. You have control here.
On a third level, like C and C++, Rust may optimize this in whatever way it wants. Your function may get inlined, and there's no passing whatsoever. Large values may be allocated on the parent's stack frame and a pointer may be passed instead.
In fact Java will leak memory over FFI boundaries as well. Rust is memory safe, but like many languages it is not leak safe. It is possible in Rust to tell it to leak memory, but that’s for special cases.
To state this another way, memory leaks are different from memory safety.
If you use reference counting in Rust, it will not be able to detect cycles. That said, it's not super easy to get a cycle accidentally.