| |
Learn enough C to encounter the pain points C++ & Rust are trying to solve. C is simple, but not easy. Then learn enough Rust to be productive. Then learn enough C++ to interact with the enormous amounts of existing C++ code. Once you know a bit of all 3, decide where to focus based on the work you're trying to do. Rust is the easiest, but least widely supported. C is necessary either way, unless you're working for Microsoft where C++ fills that role. | | | |
> C is simple, but not easy. I think I know what you're trying to say, but unfortunately C is anything but simple. The semantics are riddled with surprises of all kinds. It's unlikely the average C programmer has never written a program with undefined behavior, for example. | | | |
Right. But you can fumble through writing a lot of (maybe subtly wrong) C code. I certainly did for many years. You can't fumble through writing Rust. Its an order of magnitude more difficult to learn because the compiler won't compile "bad" code. Unlike C all the pain happens up front. Rust hurts when you're learning it. C hurts when you're trying to debug your program. If you've never written low level code before, I wouldn't start with rust. Zig or C are both much better options when getting started. But learning rust eventually will make you a better programmer. | | | |
I'm not sure I agree: the biggest pain point of Rust is that it makes some common patterns from C and C++ non-representable (outside of unsafe). People coming from other languages will likely encounter patterns they are used to that are cumbersome but still representable, with a judicious use of Box, Arc, RwLock, and friends. This means that someone that wants to "just" get something working can implement the same things they were used to, just with a nagging reminder that "they are not being as efficient as they could be" (which annoys a certain kind of people, a category I belong to too). And from this I believe that people that start learning programming with Rust will have a hard time understanding the reason for a lot of its rules, but will not have a hard time following and relying on them. I am convinced that the people with the worst Rust learning experience are experienced low level developers, looking for high performance, that have been experts for so long that they have forgotten what "learning something new" feels like. With all that said, there are lots of things that Rust could do to make itself easier to learn. I believe it will get there, in time. | | | |
> It's unlikely the average C programmer has never written a program with undefined behavior I'd add that the average C programmer can't spot UBs before they cause a problem, they are not your average bug. Unless you're very well read or are just told about them, that is. | | | |
> the average C programmer can't spot UBs before they cause a problem No, the average C programmer probably can spot some UBs, but I think there are many more undefined behaviors than most C programmers realize. John Regehr wrote a series of blog posts about some of the trickier UBs, starting here: https://blog.regehr.org/archives/213 | | | |
Looking at my own comment a day later and I think I had misread the parent comment as suggesting that "the average C programmer can spot UBs", which I felt the need to dispute. I dunno how I made that mistake even after copy/pasting, but I did, so I apologize for that. | | | |
I'd go so far as to say it's unlikely the average C programmer has ever written a program with defined behaviour. | | | |
So my 5k lines of C code project from back in college is all... undefined? | | | |
Almost certainly. Of course the only way you'll find out is when you upgrade GCC and it suddenly leaks the contents of your memory to the whole world. | | | |
No need to wait for that, running it through valgrind will surely uncover some UBs (but do note that even if it didn’t uncover anything, it doesn’t prove their absence, only that this particular run through the state space didn’t encounter any valgrind looks for). | | | |
Yes, I suppose "small" might be clearer than "simple". C is simple partly because of how little of what a computer can do it actually defines. That makes it more difficult to use, but the well-defined language is quite simple. Safe Rust tries to define all of that, so you can do everything the underlying processor is capable of, and becomes far more complex in return. | | | |
As a newcomer to systems programming, I hopped on the Rust hype train and enjoyed the ride for quite a while. But in recent months, I've been more drawn to Zig. Simplicity, vision and governance of the language (among many other things) are very much to my liking. As a bonus, I can use Zig to interact with C (even compile C, Zig is also a toolchain), so I feel I'm bound to learn more about C on this journey, too. In your position, I'd also check out Zig, just to see if it's more your cup of tea or not. Here's a good intro talk: https://youtu.be/YXrb-DqsBNU | | | |
C is the best choice to learn systems programming. C is the simplest of C, C++, or Rust and most other languages have C interop or foreign function interface. First learn C and then you can learn Rust or C++ if needed. The concepts you learn in C programming will help you to better understand the more advanced concepts used in Rust or C++. Recommended first book: https://en.wikipedia.org/wiki/The_C_Programming_Language | | | |
Agree that C is easier to explore the concepts in, but knowing enough C to know why e.g. [0] gets the behavior it does is IMO not useful if the goal is to learn the _concepts_ before switching to a language that doesn't do this kind of thing. So, IMO worth jumping to Rust as soon as you feel confident with the stack vs the heap, use-after-frees, etc. [0]: https://godbolt.org/z/x7bf4edo9 | | | |
You lost me here with p += (&ys[2] - &xs[0]); I mean, why would I expect the parenthesised subtraction to do anything I can rely on? Or did you mean (&ys[2] - &ys[0]) ? | | | |
Because if memory is a big array of bytes and pointers are indices into that big array, that should be numerically valid. And indeed, if instead of making xs and ys separate arrays, one writes: int zs[20] = {0}; int *xs = &zs[0], *ys = &zs[10];
then no perplexing behavior is observed. Of course, any veteran C programmer will point at that being UB, but the big-array-of-bytes (with holes) model is what one sees in userspace assembly, and what I assume most systems programmers think about most of the time, rather than the C memory model. I wouldn't recommend that someone trying to learn systems programming (as opposed to C or C++ as languages) spend, like, any time whatsoever memorizing the list of C UBs (and then learning about pointer provenance, exposed addresses, etc., I suppose?). Assembly, Forth, safe Go, and safe Rust don't have the same kind of UB, and (Linux) kernel C has a different set of UBs than userspace/spec-compliant C. | | | |
Why always give the most contort examples? Jeez... OP is asking about a language to learn. Let them fail, to learn and understand. And after all, C was and actually is moving the world forward, and thanks to C you have the possibility to write and run programs in Rust. Rust can't do anything if not by calling C APIs. Removing C code from any system is removing the legs from the crab. So why not choosing C? Seems wise to me. Think about the thousands of lines of C code executing across the world so you can just download a crate. It seems to me that your first interest, as a Rust user, is to have C programmers around so you can run your Rust code. I'll contribute by telling my personal experience after 20 years of embedded and system C: the fear spread by Rustaceans (regarding UB) is uber-exaggerated, specially at learning stages. Before you bring exploits up, not every C application is connected to internet or has a user interface or runs as root. Just a portion. | | | |
Same here. Embedded programmer, primary language being C. I sat here staring at that line for a solid 30 seconds trying to understand the intent - it’s nonsensical. I read your comment and see the intent you’re guessing at but… you would not generally write C like this. Pointer arithmetic is to be avoided, and this case is contrived. I don’t think a compiler warning would be thrown here though - maybe that is the point, that the language and compiler would not catch this issue and that typos or novice learners can make compilable mistakes too easily? | | | |
I mean, this surely wouldn't be written as obviously as this in real code. It'd be somewhere in a data structure that packs to be able to fit better in a cacheline, and has something like: struct edge { struct node* src; int dst_offset; int label; };
Knowing about integer overflow, the original programmer carefully wrote overflow checks where dst_offset gets computed, and the code was correct. Nodes were allocated into one contiguous array, and edges were allocated into another. Later, someone else changed how nodes were allocated, so adding a new node would never trigger a realloc of the whole graph. Instead, a linked list of node arrays is allocated. Suddenly now, computing dst_offset is UB, and yet, the observed behavior of the resulting program is the same. Even later, someone updated GCC on the CI machine, and now the inliner is more aggressive. Suddenly, mutations to node labels are unreliable, and edges are found to not refer to any node in the graph, except when debug logging logs the address they actually refer to. This is the kinda thing that requires an understanding of UB that has nothing to do with the actual hardware to diagnose and fix, which I think is really unnecessary for a beginner. | | | |
Biases because that's how I learned, but yeah... I agree. Learning C will help you be a better programmer in any C-like language. C forces you to think like a programmer. Once you grasp that, you can learn most other languages with relative ease. Maybe not counting Lisps. | | | |
I highly recommend learning enough C that you at least decently understand pointers. That’s a major stumbling block and worth overcoming. | | | |
+1 and I say this as a rust fan. The rust programmers that learned C first always have very elegant solutions and have the least issues in their unsafe blocks. C to learn how memory works and how to manage resources, then Rust to understand how to use a higher level systems language for more productivity in places where it helps. I work as a C++ dev and there are jobs in it, but unless you are trying to work a C++ job, the other two are wayyy better. | | | |
Another rust fan here (with 3 years experience writing it full time at this point). I agree with all of this. C knowledge also helps to understand what rust's borrow checker is actually doing. And it will teach you what Box / Rc / etc are for. Its surprisingly easy to write rust code that Box everywhere, and runs very slowly in practice. (I've seen rust programs run slower than their javascript equivalents). Learning C first will give you the right knowledge base to appreciate rust and use it well. | | | |
C is a good learning language for systems programming, no doubt about it. All of the elementary concepts are exposed in a simple way that will server you well in any systems programming language. It was also a language design that informed many of the systems language designs that came later, so the history is educational. However, it is not a productive systems programming language these days compared to C++, Rust, or Zig. | | | |
I would choose Rust because it has much better developer ergonomics than C. Even the little things like having a standard linter and package manager (Cargo) go a long way in writing idiomatic code. You can't go wrong with either choice, so choose the one that makes you most productive. | | | |
This is an underrated advantage of Rust. And, I can expect code to compile (or compile + flash for embedded) by running `cargo run` on Linux or Windows, vice expecting a dependency mess with the C toolchains I've encountered. | | | |
The key to any of embedded, system, or low-level programming is as much about learning how the computer actually works to a reasonable degree. You are not programming to an abstract computation model. You are moving bits and bytes around and generating assembly code and talking to the machine at quite a low-level. So while Rust and C++ are higher-level languages with all kinds of nice features to be safer, concise, structured, etc. they are not good at all for learning how to work at this low-level. Wielding either of them well for systems code is quite advanced. C and assembly are where you really need to start. | | | |
C is an abstract computer as much as any virtual machine. Now this computer is quite simple compared to the abstract machines of Rust and C++ but is doesn’t have much to do with how a computer actually works. | | | |
Disagree, C is not at all lower level than Rust or C++. It is much less expressive, but there is nothing that would be possible in C that can’t be done in C++/Rust. Hell, these latter two are even closer to the hardware due to having proper SIMD primitives. C++ is the de facto “most performant” language for a reason. | | | |
c doesn’t have any good game engines or physics libraries. missing other stuff too. otherwise it’s perfect. passing structs to functions is probably the way, but a struct with functions works too. | | | |
This question is bad. Learn all three. You’re not taking a class at school. There is no penalty for dropping out. Just start learning and if it’s boring, stop. | | | | | |
C. It's the only language people writing bootloaders, writing kernels, and writing serious userland code kinda sorta agree on. Maybe someday it'll be rust, but if so that day isn't this year or next. Plenty of time to pick it up for your next job. | | | |
C is forever. It’s the only language where the community aspires to target as early a specification as they can. This keeps C in the hands of the programmers and not the compiler creators. | | | |
I assume this question is about which language to learn, rather than which language to use for a particular project assuming you're at least somewhat familiar with all three. C++ is not a bad place to start, as any decent course in it will, after the basic concepts of variables, loops, arrays, functions, pointers and memory, algorithms & data structures, introduce you to both classes and inheritance and encapsulation (the typical object-oriented paradigm) as well as how to write functions with nested lambdas (the basic functional paradigm). From there you can pick up almost any 'higher-level' language like Java, Python, etc. that utilizes some kind of virtual machine/interpreter to run the code, the various pure functional languages and so on. Learning C and/or Rust is then a lot more accessible I think. You'll have some grasp of why 'memory-safe' Rust became fairly popular, and of why C's more low-level and simpler approach (no classes, inheritance, etc.) relying on structs, function pointers, etc. instead is still attractive, flaws and all. The underlying theme though is that these languages all compile directly without any Python-like 'bytecode' intermediate, so probably at some point diving into the assembly output for specific platforms (x86-64, ARM, RISC-V) will be worthwhile, for which godbolt.org is the place to go. | | | |
Try assembly (maybe RISC-V), then C. | | | |
For low-level programming in 2023 I would honestly start with Zig. It offers the same system programming capabilities as C but makes it much easier to write correct programs. (If Rust is the “better C++”, then Zig is the “better C”). | | | |
It really depends on the kind of systems programming you are doing. As a rough heuristic, C for embedded because portability across odd silicon; C++ for complex software close to the metal, like database kernels; Rust for somewhat higher level software — not so close to the metal — where performance matters. Right tool for the job, and all that. All are good choices in the right context. None is unambiguously superior for all systems programming use cases. | | | |
I'd be interested to hear if anyone has learned Rust without first understanding memory addressing and pointers. Although I think Rust is a better way to write safe code and the industry is right to adopt it, it seems like a heavy lift to learn about heaps, stacks and pointers whilst also learning about ownership and lifetimes. I don't really think that C++ is worth learning unless you have to work on an existing C++ codebase. | | | |
If you are targeting embedded devices, I'd go with C++. If you are targeting super efficient cloud services, I'd go with Rust. | | | |
I think learning for the sake of learning is highly overrated in programming. Would a woodworker advice anyone to learn wood working from a book? Find a project that you think is important and work on it. Doesn’t matter which language. You can always switch. Knowing C++ will make you a better Rust programmer and vice versa. | | | |
Learn all three but write only Rust unless no choice. | | | | |
Asm, followed by C89, preferably writing a compiler for the latter --- it's actually not that hard, unlike C++ or Rust. | | | |
If you can afford hundreds thousands of dollars worth of static analysis tools, pick C. Otherwise go back in time and use Cyclone. Or use Ada or Rust. | | | |
rust, c++ and (to a certain extent) c are all quite high level languages. if you want to learn low-level programming, learn it for the assembly language for the processor you are interested in. this will make learing languages like c, and the others easier - i know it did it for me. | | | |
C is such a glorious language to competently develop software in, UB be damned. | | | |
so true. c is like the dark side of the force, but for great good. | | | | |
Undefined Behavior, apologies for using jargon. | | | | |
rust-analyzer, clangd, and clion are all fantastic. why not try both? cpp feels better to me now, but that could easily change. rust-analyzer is a good experience, and cpp is not exactly fast, just faster. | | | |