There is no 80/20 rule for C unsafety, other than maybe an inverted one: 80% of the unsafety of a large C program might be spread into 80% or more of the code. :)
Not knowing C very well, could you clarify what makes it unsafe? Thanks!
edit: eh sorry I wasn't thinking straight, you of course need unsafe to cast the argv itself to a type that has a seperate argc as well. Assuming such a type is available, if it's not its implementation would also have unsafe all over the place.
Maybe to answer the underlying question. What makes it unsafe is that in C it is assumed the programmer knows to keep all indexes into argv under argc. In Rust such an assumption must be made explicit by specifying "unsafe". It is idiomatic Rust to have all instances of "unsafe" in libraries whose implementation is vetted by the community, so ideally there are little to no instances of "unsafe" in the application logic itself. Rust's compiler and type system have various tricks that reduce the amount of "unsafe" you would think you'd need for even quite complex problems.
Then if you wanted to handle dynamically set environment variables you'll need to call into your libc implementation, which crosses an ffi boundary, which means rust doesn't know about what that code is doing and therefor it requires `unsafe`.
edit: Question for others - is main a separate stackframe? I actually don't recall.
In rust, unsafe means accessing memory that's already freed or unallocated or things like that. You can look up the definition for the full definition.
I think the comment you replied to mistakenly used the term "unsafe" (that's part of the reason I dislike the term; it can mean multiple things). In rust context though, it isn't unsafe to index an array that's out of bounds. I.e. if argc=10 and you call argv[99], that will crash your program but isn't considered "unsafe".
arr[n] == *(arr + n) == n[arr]
All these forms are valid C and gcc will happily compile them all without complaining.
Just scratching the surface, we have:
- The language doesn't really have vector and strings as data types, they are pointers to memory sections without any kind of protection
- All functions on the standard library deemed as safe, added as mitgation to fix possible memory corruptions have gotchas on their use, there isn't a single one that is safe, specially because all of them expect the developer to never get the buffer size parameters wrong.
- Enumerations are not type safe, decay implicitly to integers when used in numeric context, and all numeric values can be converted into an enumeration, even if there isn't a mapping available
- Implicit numeric convertions everywhere, and since there is no overflow/underflow checking, every single numeric operation can wrap around, or be the source for clever compiler optimizations
- ISO C documents at least around 200 cases of UB, where the compiler can take the liberty to optimize the code as it pleases
- Type casts that convert complex data types into others can be a source of surprises when moving across compilers and platforms
- Speaking of which, even if you restrict yourself to ISO C, without any compiler specific extensions, there are behaviours that are implementation defined, which can vary across compilers and platforms.
- Variables defined as const, aren't really constant and one can subvert their value
- There is no null checking, so whatever happens depends on the platform.
This is just a short overview, open the man page for GCC or clang and go through the list of all warnings that you can enable to try to write safer code, specially all that are enabled via -Wall and -Wextra.
All the above flaws are also present in Objective-C, Objective-C++ and C++, due to their copy-paste compatibility with C (yes C++ isn't 100% compatible).
Compilers have the freedom to place them into readonly memory segments, either in the case of static data, in the heap (mark the pages as such for safety), in embedded static const data can even be mapped into ROM chips, so who knows what happens if the compiler has decided to implement const data that way, or what the linker scripts are doing.
There's also stuff that obviously can't end up in rodata, such as const locals that are initialized from arguments or I/O. Sometimes people assume that this means that casting away constness is legal in such cases.
Actually, there is; the problem is that the transpiler can't tell the difference between code that relies on unsafety for its semantics, versus code that would still work if appropiate annotations (potentially causing function to not be callable in intended contexts), run-time checks (possibly causing code to error out on what were intended to be valid inputs), etc, were added to make it safe.
80% of the code contains 20% of the cases where safety would require deviating from the intended semantics, not just the incidental ones. (A general-purpose transpiler can't (in general) tell the difference between intended semantics and incidental ones, so it has to conservatively assume all semantics are intended, and write everything as the most-general (ie most-unsafe) interpretation.)
Are you claiming it’s not? verify all input should be the baseline to call something safe imho.
I've translated a lot of C code to D, and manually converting `*` to `ref` (D's safe pointers), and converting to slices, cleans up most of the C code nicely and you get buffer overflow checks for free.