Some of these comparisons are a little lackluster - sure, the issues may be relatively easy to reproduce if you translate the C code 1:1 to (unsafe) Rust, but you wouldn't even have such code in the first place if you wrote idiomatic Rust:
- Confusing different integer types is much harder not only because the type checker will complain, but also because you don't really come across anything other than usize/isize unless it's a field in a protocol or something.
- Strings in rust are not zero-terminated, so that specific example might not even happen at all.
- Integers, pointers and references are not implicitly cast to booleans, so you can't accidentally mix up comparing a pointer to NULL (which is only ever required for FFI calls) and comparing an integer to 0.
- You have proper types to indicate that a function encountered an error, so all the issues stemming from checking the integer return code the wrong way (e.g. interpreting an error value as a return value) don't exist.
- You simply don't have to invoke potentially dangerous functions like `memcpy` because there are abstractions that take care of this safely and more intuitively.
That's not to say that idiomatic Rust code wouldn't come with its own problems, but they tend to be much more concentrated to fundamental logic bugs rather than silly avoidable things like overflows or misinterpreting values.
> - Confusing different integer types is much harder not only because the type checker will complain, but also because you don't really come across anything other than usize/isize unless it's a field in a protocol or something.
For me it's the opposite: usize is only used for indexing arrays, isize only used for memory offsets (which is a really niche use case outside of low level code), and most application code should be written with u32, u64, i32 and i64. Why is that? Because usize has a platform-dependent size and generally the logic of the program should be platform-agnostic.
So you often need to index something based on an u32 (or something) and then there's a cast. How do avoid it?
Well first off, your own types can implement Index instead of Index. But better yet, you can have an Idx type that represents an index, and then implement Index. That's seen for example in the petgraph API [0], you can index an graph with either a node index [1] or an edge index [2]
[0] https://docs.rs/petgraph/latest/petgraph/graph/struct.Graph....
[1] https://docs.rs/petgraph/latest/petgraph/graph/struct.Graph....
[2] https://docs.rs/petgraph/latest/petgraph/graph/struct.Graph....
> Because usize has a platform-dependent size and generally the logic of the program should be platform-agnostic.
Fair point, but it really only matters if the overflow is handled gracefully. If it's handled as an unrecoverable error condition, it often 1. doesn't happen during normal operation and 2. can be invoked by a determined attacker, regardless of the exact size. Sure, the exact characteristics might be different, but in the end it doesn't make much of a difference.
Petgraph has a great API, and in general using newtypes over integers for collection indices is a great way to avoid another logic bug: mixing up indices for different collections.
Rust has a design pattern called lifetime branding (also called generativity), which uses phony lifetimes to prevent, at compile time, confusing indexes of two separate collections of the same type. This can also enable disabling out of bounds checks without triggering UB (because, with branding, we can be sure that the index is on bounds at the moment of creating it; essentially we move bounds check from the indexing time to the index creation time)
Here's an earlier mention of that [0] (7 years ago), and here's a crate from 3 years ago, indexing [1] but I'm not sure about recent developments on that.
Now, petgraph doesn't use branding for its index types, so if you have two graphs you can confuse their indexes. On the other hand, petgraph was specifically designed so that you can reuse the node and edge numbering across many graphs (so that if you have a subgraph for example, the nodes and edges share the same ids), in this situation it's kind of hard to use branding
There's another pattern for not confusing index types which is to make different index types for each different collection and make the collection work only with that type; this is done eg. in typed-indexed-collections [2] - but it doesn't use branding so two collections with same index type have interchangeable indexes
Anyway right now this stuff is mostly folklore but I wish it were more used.
[0] https://www.reddit.com/r/rust/comments/3oo0oe/sound_unchecke...
[1] https://github.com/bluss/indexing https://docs.rs/indexing/0.4.1/indexing/
https://crates.io/crates/typed-index-collections https://www.reddit.com/r/rust/comments/hr6xcu/announcing_typ...