Rust-first programmer lists the C quirks that trip up Rust users

BD103 says the first systems programming language they ever learned was Rust, which is unusual, and that while there are plenty of "Rust for C Programmers" articles, there are few or none going the other way. Having recently started learning C and C++, they wrote a collection of eyebrow-raising details about C. The post is explicitly not a substitute for a proper C tutorial, and C++ is left out.

Booleans come first. Original C had no boolean type; programs used the integers 0 and 1. C99 added booleans in the optional <stdbool.h> header, but true and false are not literals or keywords there: they are definitions that expand to 1 and 0. C23 made booleans true language primitives, although code built for earlier standards still needs the header. A footnote adds that Clang still appears to have only partial C23 support.

Strings get the longest treatment. A Rust &str is 16 bytes (8 for the memory address, 8 for the length), while a normal reference is 8 bytes. That makes fetching the length cheap but costs memory. C stores no length and ends every string with a null byte, which the author calls an intentional trade-off. Programs need no separate size variable, but every string needs room for the terminator (so an empty string still takes one byte), null bytes cannot easily sit in the middle of a string without breaking <string.h> functions, and forgetting the terminator can cause out-of-bounds reads. The author shows a C string-reversal function that must allocate len + 1 bytes and write '\0' at the end, then the Rust equivalent, which needs neither step. A footnote says the Rust version is not idiomatic and handles only ASCII; a production version would be one line: forward.chars().rev().collect::().

Integer widths depend on the target platform. The author says long being 64 bits on Unix and 32 bits on Windows particularly irks them, and passes on a friend's advice: if cross-platform compatibility matters, use only the fixed-width integers from <stdint.h>.

Error handling is where the author is most critical. They love Rust's Result, sum types and match; C seems to boil down to functions returning a magic integer such as -1 or a null pointer, with errno (a thread-local integer) offering a bit more detail but no real error messages or stack traces. The language never forces you to handle errors. malloc() can fail and return a null pointer, and if memory runs out the later write to reversed[i] will segfault. The program should check for NULL, print a message with perror and exit. The author tried recreating Result in C with tagged unions, found it a complete mess to use, and noted nothing stops code from reading the pointer field without checking the tag. They say C's lack of private and public modifiers appears to be an intentional design decision, because C places full trust in the programmer. They personally disagree and would rather encode requirements in the type system so the compiler checks them.

Smaller items follow. C has two field access operators: . for values and -> for pointers, where Rust uses . for everything. Arrays are plain values with a known sizeof inside the function that declares them, but once passed to a function they are implicitly converted to a pointer to the first element. In the author's example, two 3-byte arrays report 3 bytes in main and 8 bytes inside the function, because 8 bytes is the size of the pointer. Clang warns when sizeof is applied to the pointer form of an array parameter. Finally, C has no borrow checking and no references, only raw pointers, which allows pointer arithmetic such as looping with an int* from &array[0] to &array[6]; the author is not sure that is a good idea and warns that pointer mistakes cause buffer overflows and out-of-bounds writes, which threaten security.

The author concludes they had a lot of fun learning C. They doubt they will use it for personal projects, but call it a crucial language for a systems programmer to know. All the examples are on GitHub.

Key facts

  • BD103 learned Rust as their first systems language and wrote the post from that angle, covering C quirks they hit while teaching themselves C; it is not a C tutorial and leaves out C++.
  • C strings end in a null byte instead of carrying a length (a Rust &str is 16 bytes: 8 for the address, 8 for the length), so an empty string still takes one byte and the programmer must allocate and write the terminator.
  • Integer widths vary by platform (long is 64 bits on Unix and 32 on Windows); the author's advice, from a friend, is to use the fixed-width types in <stdint.h> for portability.
  • C never forces error handling: malloc() can return a null pointer, and the author recommends checking for it and exiting gracefully rather than segfaulting.
  • Arrays passed to a function decay to pointers: two 3-byte arrays report 8 bytes of sizeof inside the function, the size of the pointer, and Clang warns about it.

Why it matters

Most guides run from C to Rust. This one goes the other direction, written by someone who met C after Rust, so it names the points where Rust habits stop working: no length stored with strings, no enforced error handling, no references, and array sizes that change meaning once an array crosses a function boundary. It is a personal list rather than a new finding, but each item is shown with a short code example.

Who it affects

Programmers who know Rust and are starting to read or write C, and anyone who wants to see how a Rust-trained mind reacts to C's design. The author also frames it as relevant to systems programmers in general, calling C a crucial language to know.

How to use it

Read it as a checklist of habits to unlearn. Concretely, the author suggests using the fixed-width integers from <stdint.h> when portability matters, checking every malloc() result for NULL and exiting with an error message, and allocating one extra byte for each string's null terminator. Separately, Clang happens to warn when sizeof is applied to the pointer form of an array parameter. The author's example programs are on GitHub for anyone who wants to run them.

How solid is it

This is a single personal blog post by a self-described learner, not a specification or a study. The claims are hedged where the author is unsure: C's missing visibility modifiers "appear to be" intentional, and Clang's C23 support is described with "It looks like". A footnote says the author does not fully understand how usize and isize differ from size_t and ptrdiff_t and recommends doing your own research. The Rust string-reversal example is flagged by the author as non-idiomatic and ASCII-only. Preferences such as disliking C's error handling are stated as personal opinion. No benchmarks are given.

Risks and caveats

The 8-byte pointer size and the 16-byte &str size are what the author's examples show, and the author notes that C integer widths differ by platform, so do not treat any one size as universal. The article is not a complete C guide and says so. The author's own warning applies to the pointer arithmetic example: mistakes with memory lead to buffer overflows and out-of-bounds writes, which are security threats. Their attempt to imitate Result with tagged unions did not prevent unchecked access, so it is not a safe pattern to copy.

“C places full trust in the programmer to do things right™ and gives few facilities for contracts or safe abstractions.”

— BD103, C for Rust Programmers