It can be annoying to write "safe" code, but once it meets a certain standard I can be confident in multithreaded applications I write.
I would like to use rust to write android apps. I don't really like the whole android studio java thing.
I expect that Google is using neither of these for most of their own code, but rather their own build system (which I think is the same between the languages).
I absolutely agree if you aren't Google though.
If I were to go to another company I’d promote using either of the above.
Proprietary code uses the internal version of Bazel: https://bazel.build/
That is an understatement. I can't think of a build system that has spawned more attempts to find something better than CMake has.
There have been so many people trying to make their own C/C++ build system and/or package manager out of sheer spite for CMake that it's frankly hard to keep track.
In fairness to them and to CMake, it's not a simple problem to solve. To truly know CMake is to wish you didn't, that way someone else would have to maintain it.
At least it knows how to use ninja though.
But I’d take cargo over any of this stuff. Cargo means I don’t have to think about compiler flags at all.
For me the ideal syntax is C/Go, just to be clear what I like.
But I agree that the tooling that cargo introduced is a breath of fresh air in a world dominated by huge makefiles, libraries copied in the repository (I know, there is Conan, vcpkg etc)...
Rust has plenty of syntax coming straight from ML (okay, not straight, probably through OCaML)
Pattern matching, destructuring, expressions everywhere, etc are stuff that C/Go never even heard about.
But if I was working in C++ and had a choice of C++ or Rust, I’d go Rust based on this.
Go get this completely wrong! It use a tuple rather than an enum for potential errors. This means you can forget to check errors and just use the invalid (nil?) return value from the function.
On the other hand, rust uses the Either enum to force you to handle the error. Alternatively you can use the ? operator to pass it to the calling function, which is reflected in the enclosing function's type.
It's a product of functional programming, and for me I can't see how you would ever want to handle errors outside of the functional programming railway-oriented style. For me it's just superior.
It's absolutely possible to compute wrong pointer offsets. It's absolutely possible to dereference nil. It's absolutely possible to bork ownership and have multiple thread trample a struct. It's absolutely possible to reinterpret memory the wrong way.
I do agree that UAF is not possible (in most cases) due to the GC. That array indexing out of bounds is not possible. But it is by no means "memory safe" to the level Rust is.
This is specifically the one place where go is not memory safe IMO.
> It's absolutely possible to compute wrong pointer offsets.
In Go? Without the `unsafe` package (at which point you are explicitly opting out)? How? There's no pointer offset in the first place.
> It's absolutely possible to dereference nil.
Yeah, but that's safe defined behavior in go equivalent to "unwrap"ing a None option in rust. It reliably crashes. It's not like C where it's undefined behavior and you might crash of you might just corrupt random memory or have the optimizer make your code do something even stranger.
It's (really the lack of non-nil types) is one of many reasons why go doesn't produce as reliable software as rust, but it's not a memory safety issue.
> It's absolutely possible to reinterpret memory the wrong way.
Again, without the unsafe package? How?
>> It's absolutely possible to reinterpret memory the wrong way. > Again, without the unsafe package? How?
Again my go is rusty, but I saw quite a bit of shenanigans in go with pointer casting from what's essentially a collection of void*. However perhaps those casts blow up at runtime? I'm too used to rust where it's explicit where it'll blow up.
>> It's absolutely possible to dereference nil. > Yeah, but that's safe defined behavior in go equivalent to "unwrap"ing a None option in rust. It reliably crashes. It's not like C where it's undefined behavior and you might crash of you might just corrupt random memory or have the optimizer make your code do something even stranger.
Agreed. I conflated "safety" and "robustness" here. The existence of nil is a thorn in the language.
Thanks for the corrections!
The other big thing that makes it spiritually like Go is that you follow the same pattern of: allocate resource, immediately defer deallocation.
I mean, at that point pretty much every language would be a decent choice.
>great error handling
why people so confident being so wrong???
Until you stumble upon the countless footguns. At least they generally don’t lead to memory unsafety, just garbage data.
Data Race Patterns in Go
I’m sorry if this comes across as dismissive, but I find it hard to take people seriously with complaints about syntax like this. Learning new syntax is really easy. Like, if you’re familiar with C & Go, you could probably learn all the syntax of rust in under an hour. The only surprising part of rust’s syntax is all the weird variants of match expressions.
Rust has some surprising semantics. Like how lifetimes work (and when you need to specify them explicitly). That stuff is legitimately difficult. But learning that if statements don’t need parenthesis is like - seriously whatever dude. If you want to spend your career never learning new stuff, software isn’t for you.
I picked up objective C about 15 years ago. The only thing most of my friends knew about it was that it had “that weird syntax”. It took no time at all to adjust. It’s just not that hard to type [] characters.
If you ever have to deal with lifetimes, then the verbosity goes up pretty significantly. And, dealing with pointers ultimately introduces multiple layers of concepts that you don't necessarily run into with C++/Go/Java.
Yet, with the type inference by default, rust can often feel shockingly terse. Doing collection manipulation is just as terse as any language you could imagine.
I think that's probably where complaints about syntax comes in.
C++ hides a lot of that complexity with move semantics, shorthands, autocoersion, and by making lifetime issues runtime problems. Languages like Go/Java/Python simply push everything onto the heap and try to avoid exposing low level memory semantics.
It's easy for me to see why someone wouldn't like rust. I think it's fine, but I'm also brain broken from perl and php.
I find rust generally more terse than both C and - especially Go, to which rust was compared upthread. Writing out lifetimes explicitly can be confusing. But I don't think it adds that much syntactic noise. Maybe 1/20 functions in my code have explicit lifetimes. That will confuse beginners, but I don't think its too bad once you're used to them.
> And, dealing with pointers ultimately introduces multiple layers of concepts that you don't necessarily run into with C++/Go/Java.
This is my #1 complaint about rust's syntax. If you ever need to interact with raw pointers within unsafe blocks, I feel like the language really fights you. It seems so strange, too - since unsafe blocks are exactly the place where it matters the most that my code is easy to read. Improving the syntax around pointers (for example by adding C's -> operator) would make unsafe blocks clearer.
Distributing Rust software is the pain that it is mostly because of how Cargo works. It’s pretty much impossible to sanely distribute something that isn’t a headache for downstream to consume.
"Absolutely awful" strikes me as wild hyperbole -- you also meant it this way as well, right? What toolchains are not absolutely awful in your mind?
Cargo isn't perfect by any stretch of the imagination -- there are a few common complaints that people have and a bunch of sharp edges (most of which have a github issue that has been open for years), but... "absolutely awful" has to be hyperbole.
But that's just not how Rust works: it's trying to fit a square peg in a round hole, and it isn't Cargo's fault that you have trouble with it.
I’ve found it a joy to use compared to CMake and friends. How does it make it harder to consume something downstream? Seems easy enough to me - just share the source crate.
Are you trying to distribute pre compiled code or something like that? I can see how that would be harder - cargo doesn’t really support that use case.
How would you improve cargo?
That’s a nice concrete example of something that sounds simple but is a nightmare.
Let’s be clear: the goal is to distribute a tarball which the receiver can build. Crates won’t be packaged in the target host (that’s part of Rust’s design), so we don’t have any choice but to include them too.
But there’s no simple way of gathering all of these. “cargo vendor” fetches all transitive dependencies for all platforms. So if any dependency supports windows, you end up with 400MB(!) of windows-only dependencies, even if your project doesn’t target windows. This makes “cargo target” useless.
There’s “cargo-vendor-filtered”, a huge hack around this bug, but it’s also broke in subtle ways.
In the end, if you want to distribute a tarball which a downstream can build, you can’t. Cargo works online only.
Like I said: cargo is too opaque. There’s no command to generate a list of files that it would download for a build. There’s no command to fetch all “real” dependencies. It too opaque an monolithic, doing everything in one indivisible way. This is a great experience for the developer, but awful for anyone else.
Thanks for clearing that up. What problem does that solve? I've never tried to do that, but I can see how it would be a pain in the neck.
I wonder how hard that would be to fix. It doesn't sound like a difficult feature to implement in cargo. I wonder how amenable the cargo devs would be to adding something like that?
Cargo is a blessing for any source-available project. All bundled up, a `cargo build` away. Because don't you dare say CMake or autotools are better, that's just the stockholm syndrome kicking in because you're familiar with it.
Seriously, how a CMakeLists.txt can even be compared to a Cargo.toml file? One is imperative full of arcane conditions everywhere filled with boilerplate, while Cargo.toml is a declarative manifest of the package?
Though there is one very sore point when distributing software, and that is for distribution package maintainers, because the whole ecosystem has been built around the C model and dynamic linking. That is not even the fault of cargo, since Rust's ABI is not stable thus dynamic linking would not work most of the time. Another thorn is generic stuff, which needs to be monomorphized, and as such don't work with dynamic linking (without Box<dyn>); C++ actually has the same issue and is why there are so many "header only" libraries for it.
Personally I’m relieved that we’re starting to see real competition to the C & C++ duopoly. For awhile there all the new languages were GC, and paid for their shiny features with poor runtime performance. (Eg java, C#, Ruby, Python, lua, go, etc etc)
Rust is a fine language. Personally I can’t wait to see what comes after it. I’m sure there’s even better ways to implement some of rust’s features. I hope someone out there is clever enough to figure them out.
Even in its own "memory safety" definition, which is the first result on Google, they criticize C instead of providing a proper definition:
I'm not sure that page is intended to provide a definition of "memory safety" in the first place? It (and the following page) seem more intended to introduce safe/unsafe Rust and the boundaries between the two.
It's also from the Rustinomicon, which states:
> Unlike The Rust Programming Language, we will be assuming considerable prior knowledge. In particular, you should be comfortable with basic systems programming and Rust.
So it's arguably unsurprising that a definition of memory safety would not be found there.
My guess is that if you want a more precise definition you'd want to look at the Rust Reference (e.g., [0]) or in related areas.
The Rust literature is poorly written compared to C and Ada and the argumentation style on forums is sloppy, aggressive, and often unintelligible.
Which is a pity, because the language itself does not seem to be so bad.
I'm confused, because that's not been my experience of the rust community at all. I've been very critical of certain aspects of rust over the last few years, and I've (for the most part) gotten fair, reasonable feedback in response.
> The Rust literature is poorly written compared to C and Ada
I'm even more confused. Which literature are you looking at? Can you provide some examples so we’re all talking about the same thing?
I find most of the documentation around rust to be the best in the business. Eg here's the reference documentation for iterator trait, in the standard library:
https://doc.rust-lang.org/std/iter/trait.Iterator.html
Every function in that interface is well documented, with examples.
Here's the equivalent for C++:
https://en.cppreference.com/w/cpp/iterator/iterator.html
Where is the rest of it? This doesn't describe how iterators work at all. Or how to use them. There's more stuff in the header file but its inadequate by far.
So much C library code is documented in ad-hoc ways - often through doxygen, which is a disaster. Eg here's the documentation for LMDB. LMDB is one of the most thoroughly documented C APIs I've seen, but I find this almost totally unusable. I often find myself reading the source instead. There's not even any links to the source from here:
http://www.lmdb.tech/doc/group__mdb.html
In rust, any published crate automatically has a docs.rs/cratename link. Eg for serde's reference manual:
And then for the "guide" style explanation they wrote a book:
Where is any documentation for the C standard library? As far as I can tell, there's no official documentation at all. There are man pages. But in comparison to rust's docs, or mdn for javascript, man pages are nowhere near as good. I’d give examples but this comment is too long already.
> http://www.lmdb.tech/doc/group__mdb.html
How is doxygen a disaster?
Why do we need links to the source code? Doxygen is already embedded in the source, you should already be reading the source code on your local machine. It makes no sense to go searching across the web for information that's already stored on your local machine. Especially since you have no idea if the version you find on the web matches the version you're using locally.
> How is doxygen a disaster?
I've just never read any good, user friendly documentation ever produced by doxygen. Even when I used it myself. It always comes out looking like a pig's breakfast.
Like, take the lmdb docs. And I'm sorry for picking on it. But its a good example, because you've clearly put effort into using doxygen to document lmdb. I think the lmdb docs are about the best that doxygen generated documentation gets.
Looking at this page: http://www.lmdb.tech/doc/group__mdb.html
There's a bunch of concepts here:
- Environment (mdb_env)
- Database and database (dbi)
- Transaction
- Cursor
- Record
- Key
But none of those concepts are defined or explained. They're simply referenced without explanation in a big jumble of function names and descriptions, leaving me to figure out how they're supposed to work together. Maybe if those data types were defined up the top of the page? No. Doxygen tries to put some data structures at the top of the page - but for some reason the only documented types are MDB_val, MDB_stat and MDB_envinfo. All terrible places to start reading if you want to understand how to use lmdb.
Good documentation would lead with some front matter like:
- What does the library do
- How does the library sees the world. Here, explain the above concepts and how they relate. (Eg an environment represents a set of files on disk. Each environment contains a numbered set of databases, database contains a set of records. You can read and write within a txn. You can use a cursor to iterate. ... Etc)
- Code examples of all of the above. Ideally a hello world, and more complex / specific examples showing each feature.
Doxygen does not help with any of that. From this documentation I don't know how to use lmdb to make a correct program. I can kinda guess how to use various features, but not what the features actually are or how to use them correctly.
For example, I can obliquely tell from reading the function descriptions that an environment can be opened multiple times at the same time. But I have no idea why I'd want to do that, or how, or what performance implications there are, or if there are any gotchas I need to be aware of if I do that. I see there's a bunch of functions for mdb_env_copy. Does that copy in memory or on disk? Does it do it atomically, like at a snapshot? Is it synchronous? Does it fsync? fdatasync? What errors can happen? The documentation isn't helpful.
So I'm not just banging on about rust, here's equivalent - but much better - reference documentation for prisma:
https://www.prisma.io/docs/orm/reference/prisma-client-refer...
Prisma also has a separate guide, explaining the concepts involved:
https://www.prisma.io/docs/orm/prisma-client/queries/select-...
Or for another positive example, here's bun's documentation on using sqlite from javascript:
https://bun.com/docs/runtime/sqlite
They explain the concepts and present examples for how to use all the features. When I read those docs, I come away knowing how to use the library to solve my problems. I don't have that experience reading the lmdb documentation.
Maybe its possible to produce good documentation using doxygen. But I've never seen it done. Not even once.
-----
Side points:
> you should already be reading the source code on your local machine.
I'd rather not read the source code of all my dependencies to understand how to use them. Reading the source code of your dependencies should be a last resort. Eg, I don't go reading my compiler's source code if I want to understand my programming language. I don't want to read the source code of my web browser, or postgres, or linux, or any of this stuff.
> It makes no sense to go searching across the web for information that's already stored on your local machine. Especially since you have no idea if the version you find on the web matches the version you're using locally.
I hear you, but honestly I don't really care where documentation lives. Just so long as I can find it and read it. But with rust in particular, if you want local documentation you can run cargo doc to generate & open the documentation of your project and all your dependencies, which is nice.
And re: versions, rust docs hosted online also have a little 'version' field up the top showing which version of the library you're looking at the documentation of. Eg if you open https://docs.rs/rand/ I see "rand-0.9.2". If you change versions, the URL changes. It'd be nice if doxygen had that too.
I'm not really sure what you expect here. Like, a large driving factor of using rust (compared to C/C++) is that it has better memory safety. Should rust not talk about that? Should we try and be careful about the feelings of C/C++ devs and not name the truth in the room around memory safety?
The reason android is moving to rust is because it decreases the memory related defect rate compared to C++. Should we shy away from talking about C++ memory bugs because they're somehow embarrassing?
When C came out, I'm sure a lot was written about how much easier it was to program in compared to assembly. Does that mean there's a feud between C and assembly? I'm sure some assembly developers felt under attack. But its not a feud. Just two tools with different use cases. That's how I see C and rust.
(doesn't mean it's not an improvement on C++)
I'd call it casting thought technically maybe it's not you might want to call it something else? You don't need transmute or leak. The issue is only 10 years old now https://github.com/rust-lang/rust/issues/25860
If the bar is "deliberately malicious code results in a segfault", get back to me when they fix
memcpy(0x10000, 0x20000, 0x10);
EDIT: and even that's being charitable; the Rust issue is viewed as a compiler bug which should be fixed.All the ways to coerce and poke the implementation of what should be safe constructs to produce unexpected garbage - and people spending time fixing the issues!
It’s like the best possible advertisement for ”we enable soundness and correctness for all your programs.”
https://github.com/rust-lang/rust/issues?q=state%3Aopen%20la...
That being said, it would be pretty easy to implement some pointer semantics even in C that can do 95% of what Rust does.
Making a language with memory-safe pointers isn't hard. Making a language with memory-safe pointers that doesn't rely on sandboxing, a virtual machine, or other dynamic checks which produce runtime overhead--thereby disqualifying one from being considered for this domain in the first place--is nontrivial.
The only way around this would be to have a absolutely strict type system that defines the finite sets of data that memory can hold.
But for compile time checks, its not hard. For example, the I would do it C is that every pointer gets an optional permission id through some syntax when created. Any expression involving modifying that pointer needs to have the appropriate permission id stated, any dereference operation needs to have the appropriate permission stated, and free is only limited to the function where the pointer was created with malloc. Then any pointer created in assignment from an expression involving that pointer inherits the permission id.
So you simply have a system of tracing where memory gets used.
But all of this is overkill tbh, when you can just use existing static memory analyzers that pretty much do the same thing. And coupled with dynamic memory analyzers like valgrind with appropriate testing, you don't even need to do runtime checks within your code.
Seems like a pretty wild claim to make in the comment thread of this article. Google has some of the most careful engineers in the business. They use valgrind & ubsan & friends religiously. And yet this is their conclusion:
> Our historical data for C and C++ shows a density of closer to 1,000 memory safety vulnerabilities per MLOC. Our Rust code is currently tracking at a density orders of magnitude lower: a more than 1000x reduction.
C is not as memory safe as rust. And it cannot be made as safe as rust with a few bolted on tools and programming tricks.
The CPU and the hardware don’t care how confident C coders are in their ability.
C developers tend to forget the reason why Windows and UNIX like systems are now quite robust is that there has been over 50 years of turd polishing. Unfortunately for rust it is not immune to bugs other than memory safety issues. I think that it is a good idea to write new code in rust. Less so for battle hardened old code.
C++ is somewhere between C and rust. With modern ‘good practices’ (no raw pointers, no for loops) it can be an order of magnitude or two safer than C.
Plenty of people don't write Rust for additional memory safety, they write Rust because the features provided by it is overall very balanced & encourages developer to write code which handles almost all edge cases.
Have fun justifying that Rust is "also" unsafe, with the right tools you can achieve the same in C++, if you're a great dev you can do even better, etc.