> I think Rust is harder to learn, but once you grok it, I don't think it's harder to use, or at least to use correctly. It's hard to write correct C because the standard tooling doesn't give you much help beyond `-Wall`.
When I say Rust is harder to use (even after learning it decently well), what I mean is that it's still easier to write a pile of C code and get it to compile than it is to write a pile of Rust code and get it to compile.
The important difference is that the easier-written C code will have a bunch of bugs in it than the Rust code will. I think that's what I mean when I say Rust is harder to use, but I'm more productive in it: I have to do so much less debugging when writing Rust, and writing and debugging C code is more difficult and takes up more time than writing the Rust code (and doing whatever less debugging is necessary there).
> Also, I highly recommend getting into the habit of running `cargo clippy` regularly. It's a wonderful tool for catching non-idiomatic code.
That's a great tip, and I usually forget to do so. On a couple of my personal projects, I have a CI step that fails the build if there are any clippy messages, but I don't use it for most of my personal projects. I do have a `cargo fmt --check` in my pre-commit hooks, but I should add clippy to that as well.
If you're using VS Code then you can add `"rust-analyzer.check.command": "clippy"` to your `settings.json`. I assume there's a similar setting for rust-analyzer in other editors.
Package managers are for running other people's code, I would not expect the same of static analysis tools, especially since they are of use while auditing other people's code before building/running it.
Cargo's threat model here is identical to that of rust analyser. If you trust your dependency tree sufficiently to run `cargo build`, then you trust it sufficiently to run rust analyser.
Rust analyser executes those exact same build scripts. This is the primary avenue for exploits in both pieces of software, and is called out explicitly in the page you originally linked.
That's a fair distinction. Basically, it's easier to write C that compiles than Rust that compiles, but it's harder to write correct C than correct Rust.
Regarding Clippy, you can also crank it up with `cargo clippy -- -Wclippy::pedantic`. Some of the advice at that level gets a little suspect. Don't just blindly follow it. It offers some nice suggestions though, like:
warning: long literal lacking separators
--> src/main.rs:94:22
|
94 | if num > 1000000000000 {
| ^^^^^^^^^^^^^ help: consider: `1_000_000_000_000`
|
> it's still easier to write a pile of C code and get it to compile than it is to write a pile of Rust code and get it to compile.
As someone who is more familiar with Rust than C: only if you grok the C build system(s). For me, getting C to build at all (esp. if I want to split it up into multiple files or use any kind of external library) is much more difficult than doing the same in Rust.
Rust allows to provide more information about types (generic types, pointer usage) and checks it, while in C you have to rely on doc comments and checking the code manually. Or am I wrong and C allows to specify pointer nullability, pointer ownership and array bounds?
None of those things feature in any problem I deal with on a daily basis, whatever language I use.
So for example today I dealt with a synchronization issue. This turned out to not be a code bug but a human misunderstanding of a protocol specification saga, which was not possible to code into a type system of any sort. The day before was a constraint network specification error. In both cases the code was entirely irrelevant to the problem.
Literally all I deal with are human problems.
My point is Rust doesn't help with these at all, however clever you get. It is no different to C, but C will give you a superset of vulnerabilities on top of that.
Fundamentally Rust solves no problems I have. Because the problems that matter are human ones. We are too obsessed with the microscopic problems of programming languages and type systems and not concentrating on making quality software which is far more than just "Rust makes all my problems go away" because it doesn't. It kills a small class of problems which aren't relevant to a lot of domains.
(incidentally the problems above are implemented in a subset of c++)
> So for example today I dealt with a synchronization issue. This turned out to not be a code bug but a human misunderstanding of a protocol specification saga, which was not possible to code into a type system of any sort.
Maybe not in a reasonable language no, but there are advances in type systems that are making ever larger classes of behaviours encodable into types. For example, algebraic effects (can this function throw, call a remote service etc)
Vulnerabilities are bugs, so the C code will have more bugs than the Rust program.
You might say that the C and Rust code will have the same number of logic errors, but I'm not convinced that's the case either. Sure, if you just directly translate the C to Rust, maybe. But if you rewrite the C program in Rust while making good use of Rust's type system, it's likely you'll have fewer logic errors in the Rust code as well.
Rust has other nice features that will help avoid bugs you might write in a C program, like most Result-returning functions in the stdlib being marked #[must_use], or match expressions being exhaustive, to name a couple things.
> most Result-returning functions in the stdlib being marked #[must_use]
Actually it's a bit cleverer than that, and some people might benefit from knowing this. The Result type itself is marked #[must_use]. If you're writing a Goat library and you are confident that just discarding a Goat is almost always a mistake regardless of the context in which they got a Goat you too should mark your Goat type #[must_use = "this `Goat` should be handled properly according to the Holy Laws of the Amazing Goat God"] and now everybody is required to do that or explicitly opt out even for their own Goat code.
Obviously don't do this for types which you can imagine reasonable people might actually discard, only the ones where every discard is a weird special case.
Types I like in Rust which help you avoid writing errors the compiler itself couldn't possibly catch:
Duration - wait are these timeouts in seconds or milliseconds? It's different on Windows? What does zero mean, forever or instant ?
std::cmp::Ordering - this Doodad is Less than the other one
OwnedFd - it's "just" a file descriptor, in C this would be an integer, except, this is always a file descriptor, it can't be "oops, we didn't open a file" or the count of lines, or anything else, we can't Add these together because that's nonsense, they're not really integers at all.
How can it not be true? One of the primary features of the rust compiler is enforcing memory safety at compile-time. C doesn't have anything like that. There are an entire class of bugs that are impossible to implement in rust.
I have written a lot of C and Rust. The notion that identical projects written in both languages would have identical numbers of (or even severity of) bugs is laughable on its face.
I mean, literally just not being able to deref a NULL pointer by itself is enormous.
You can dereference null pointers in Rust, it's also easy to make null pointers. The thing is I'm not surprised you didn't realise this is possible because you would never actually do this in Rust since it's obviously a bad idea.
let p: *const i32 = std::ptr::null(); // Pointer to a signed 32-bit integer, but it's null
unsafe { some_local = *p }; // Dereferencing the null pointer, bang
If we instead use references they're nicer to work with, don't need unsafe, perform the same, and are never null. So obviously that's what real Rust programmers almost always do.
Of course it’s possible. I am well aware it’s possible. You can also just call into a C library that hands you a null pointer. Further, it’s wholly possible to invalidate every single one of Rust’s safety guarantees if you try hard enough.
In practice I have never encountered a null pointer in Rust. Most Rust programmers have never encountered a null pointer. For most non-FFI practical purposes you can act as if they don’t exist.
When I say Rust is harder to use (even after learning it decently well), what I mean is that it's still easier to write a pile of C code and get it to compile than it is to write a pile of Rust code and get it to compile.
The important difference is that the easier-written C code will have a bunch of bugs in it than the Rust code will. I think that's what I mean when I say Rust is harder to use, but I'm more productive in it: I have to do so much less debugging when writing Rust, and writing and debugging C code is more difficult and takes up more time than writing the Rust code (and doing whatever less debugging is necessary there).
> Also, I highly recommend getting into the habit of running `cargo clippy` regularly. It's a wonderful tool for catching non-idiomatic code.
That's a great tip, and I usually forget to do so. On a couple of my personal projects, I have a CI step that fails the build if there are any clippy messages, but I don't use it for most of my personal projects. I do have a `cargo fmt --check` in my pre-commit hooks, but I should add clippy to that as well.