Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm kinda in the opposite camp. After doing a bunch of VB in my tweens and teens, I learned Java, C, and C++ in college, settling on mostly C for personal and professional projects. I became a core developer of Xfce and worked on that for 5 years.

Then I moved into backend development, where I was doing all Java, Scala, and Python. It was... dare I say... easy! Sure, these kinds of languages bring with them other problems, but I loved batteries-included standard libraries, build systems that could automatically fetch dependencies -- and oh my, such huge communities with open-source libraries for nearly anything I could imagine needing. Even if most of the build systems (maven, sbt, gradle, pip, etc.) have lots of rough edges, at least they exist.

Fast forward 12 years, and I find myself getting back in to Xfce. Ugh. C is such a pain in the ass. I keep reinventing wheels, because even if there's a third-party library, most of the time it's not packaged on many of the distros/OSes our users use. Memory leaks, NULL pointer dereferences, use-after-free, data races, terrible concurrency primitives, no tuples, no generics, primitive type system... I hate it.

I've been using Rust for other projects, and despite it being an objectively more difficult language to learn and use, I'm still much more productive in Rust than in C.




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`. Rust's normal error messages are delightfully helpful. For example, I just wrote some bad code and got:

    --> src/main.rs:45:34
     |
  45 |         actions.append(&mut func(opt.selected));
     |                             ---- ^^^^^^^^^^^^ expected `&str`, found `String`
     |                             |
     |                             arguments to this function are incorrect
     |
  help: consider borrowing here
     |
  45 |         actions.append(&mut func(&opt.selected));
     |
I even had to cheat a little to get that far, because my editor used rust-analyzer to flag the error before I had the chance to build the code.

Also, I highly recommend getting into the habit of running `cargo clippy` regularly. It's a wonderful tool for catching non-idiomatic code. I learned a lot from its suggestions on how I could improve my work.


> 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.


Neovim:

    require("lspconfig").rust_analyzer.setup({
        settings = {
            ["rust-analyzer"] = {
                checkOnSave = {
                    command = "clippy",
                    allFeatures = true,
                },
            },
        },
    })


You might want to reconsider use of rust-analyzer, it isn't safe to use on code you haven't written yourself.

https://rust-analyzer.github.io/book/security.html


> it isn't safe to use on code you haven't written yourself

Neither is cargo (nor npm, nor any other package manager, for that matter).

I'm not sure what value being that paranoid is buying you in the long run.


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.


Considering Cargo has build scripts, which are designed to run arbitrary code, that doesn't appear to be correct.

https://doc.rust-lang.org/cargo/reference/build-scripts.html


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.


Yes, and that is my complaint.


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`
     |
that you don't get by default.


You can also add #![warn(clippy::all, clippy::pedantic)] to your main.rs/lib.rs file to get those lints project-wide.


The truly pedantic setting here would by complaining about the magic number.

Why 1_000_000_000_000, what does that number mean.

It is for free to:

    let my_special_thing = 1_000_000_000_000
since the compiler will just inline it.

The readability problem was never the lack of separators, since that number might be the wrong number regardless.


Getting something to compile is never the end-goal. It takes 0 effort to get Python compile.


It can feel like a real milestone though


> 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.


I’d add that the Rust code and C code will probably have the same number of bugs. The C code will likely have some vulnerabilities on top of those.

Rust doesn’t magically make the vast majority of bugs go away. Most of bugs are entirely portable!


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)

https://koka-lang.github.io/koka/doc/index.html

linear types (this method must be called only once etc)

https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/line...

dependent typing (f(1) returning a different type from f(2), verifiable at compile time),

https://fstar-lang.org/index.html

Some of these features will eventually make it to “normal” PL. For example, Scala now has dependent types,

https://dotty.epfl.ch/docs/reference/new-types/match-types.h...

and Java can support linear type checking,

https://checkerframework.org/manual/#must-call-checker


> None of those things feature in any problem I deal with on a daily basis, whatever language I use.'

I run into those things nearly daily, so... ok then.


Do not feel bad friend, It's not you.. there is definitely inconsistencies here. Maybe they work on absolutely pristine perfect codebases.


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.


I'd say whether Rust helps you reduce bugs depends on how good you are at creating abstractions and/or encoding properties in the type system.


Most bugs are way above that level of abstraction and thought.


[Citation needed].


Only about 70% of high severity bugs[1]. Quite a negligible amount.

[1]: https://www.chromium.org/Home/chromium-security/memory-safet...


This is objectively nonsense


Interesting use of the word 'objectively' there.


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.


>> One catches far more issues than another

> akshually they have same amount of bugs

You literally segfault by dereferencing invalid pointer.


I agree.

But: If you are LUCKY! The worse case is if you DON'T segfault by dereferencing invalid pointer.


Not really.

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.


> Also, I highly recommend getting into the habit of running `cargo clippy` regularly.

You can also have that hooked up to the editor, just like `cargo check` errors. I find this to be quite useful, because i hace a hard time getting into habits, especially for thing that i'm not forced to do in some way. It's important that those Clippy lints are shown as soft warnings instead of hard errors though, as otherwise they'd be too distracting at times.


> It's hard to write correct C because the standard tooling doesn't give you much help beyond `-Wall`

I won't disagree that correct C is harder to write, but it's not 2005 anymore and standard tooling gives you access to things like asan, msan, ubsan, tsan, clang-tidy...


C-with-sanitizers is miles ahead of what C used to be, but just a couple weeks ago I ran into a dangling pointer bug that ASan doesn't catch. (Sidenote 5 here: https://jacko.io/smart_pointers.html) It seems like one of the big downsides of sanitizers is that they can't instrument other people's code, in this case the C standard library.


Agree, Rust is quite hard to learn, but now that I know it I have a hard time writing anything else. It really gives you the best of a lot of worlds.

Granted I can still crank out a python program faster, that kinda works but god forbid you need to scale it or use any sort of concurrency at all.


Go?


I do a lot of ML, and the Go memory model is highly problematic. The Go to C FFI is too slow and doesn't interface with CUDA well.


* Rust errors can be equally unhelpful. Also, the error you posted is hands down awful. It doesn't tell you what went wrong, and it's excessively naive to rely on compiler to offer a correct fix in all but the most trivial cases. When errors happen, it's a consequence of an impasse, a logical contradiction: two mutually exclusive arguments have been made: a file was assumed to exist, but was also assumed not to exist -- this is what's at the core of the error. The idiotic error that Rust compiler gave you doesn't say what were the assumptions, it just, essentially, tells you "here's the error, deal with it".

* In Rust, you will have to deal with a lot of unnecessary errors. The language is designed to make its users create a host of auxiliary entities: results, options, futures, tasks and so on. Instead of dealing with the "interesting" domain objects, the user of the language is mired in the "intricate interplay" between objects she doesn't care about. This is, in general, a woe of languages with extensive type systems, but in Rust it's a woe on a whole new level. Every program becomes a Sisyphean struggle to wrangle through all those unnecessary objects to finally get to write the actual code. Interestingly though, there's a tendency in a lot of programmers to like solving these useless problems instead of dealing with the objectives of their program (often because those objectives are boring or because programmers don't understand them, or because they have no influence over them).


I don't follow your first point—the compiler is pointing out exactly what the problem is (the argument has the incorrect type) and then telling you what you likely wanted to do (borrow the String). What would you see as a more helpful error message in this case?


The compiler says "expected X, but found Y". I don't know how to interpret this: is the type of the thing underlined with "^^^" X or Y? "Expected" and "found" are just like "up" and "down" in space: they are meaningless if you don't know what the compiler expects (and why should it?).

What it needs to say is something along the lines of "a function f is defined with type X, but is given an argument of type Y": maybe the function should be defined differently, maybe the argument needs to change -- it's up to the programmer to decide.


I dunno, I feel like if you've used a compiler regularly, "expected X, but found Y" is a pretty common idiom/shorthand that people understand. Your wordier version of that feels unnecessary to me.


C is a low level language and deals with things close to the metal. It's probably not fun to write a large business app in barebones C but you having control over low level things makes other things possible and very fast too. Depending on the type of problem you have use the appropriate and favorite language.


You cannot be serious right now.


Since it's underlining code you wrote, it must be "found" that is highlighted, not "expected". Much like up and down, gravity exists to ground all of us in the same direction.


I'm over here with TTS: Underlining in a terminal rarely translates to audio. It isn't the only consideration that needs to be made, when making things clear.


I hadn’t considered this use case, thank you for pointing it out!


except nobody has to cater to every worst case scenario


I am sure the Rust community would welcome feedback from blind developers about their error messages. Accessibility is a good thing.


One thing that could help here is that the compiler is able to offer output in JSON, allowing you to format the messages however you'd like: https://doc.rust-lang.org/rustc/json.html

I'm not aware of an existing tool to produce blind-friendly output, but this would at least be a part of that!


So every disabled programmer is now a "worst case scenario"?


Or in short, Rust isn't for disabled programmers?


I don't see any way that the use if expected and found can be ambiguous for a type conflict.

I buy a fruit mixer from Amazon.com ; I send it back along with a note: expected a 230VAC mixer, found a 110VAC mixer.


I tend to agree with this.

The code tend to be loaded with primitives that express ownership semantics or error handling. Every time something changes (for instance, you want not just read but also modify values referenced by the iterator) you have to change code in many places (you will have to invoke 'as_mut' explicitly even if you're accessing your iterator through mutable ref). This could be attributed (partially) to the lack of function overload. People believe that overload is often abused so it shouldn't be present in the "modern" language. But in languages like C++ overload also helps with const correctness and move semantics. In C++ I don't have to invoke 'as_mut' to modify value referenced by the non-const iterator because dereferencing operator has const and non-const overloads.

Async Rust is on another level of complexity compared to anything I used. The lifetimes are often necessary and everything is warpped into mutliple layers, everything is Arc<Mutex<Box<*>>>.


> Rust errors can be equally unhelpful. Also, the error you posted is hands down awful. It doesn't tell you what went wrong, and it's excessively naive to rely on compiler to offer a correct fix in all but the most trivial cases

What? It tells the user exactly what's wrong

> Every program becomes a Sisyphean struggle to wrangle through all those unnecessary objects to finally get to write the actual code

That is the cost of non-nullable types and correctness. You still have to do the Sisyphean struggle in other programming languages, but without hints from the compiler.


The biggest problem with C is that doesn't even have enough features to help you build the features and abstractions you need and want.

For example with C++ the language offers enough functionality that you can create abstractions at any level, from low level bit manipulation to high level features such as automatic memory management, high level data objects etc.

With C you can never escape the low level details. Cursed to crawl.


Just FYI.

Back in 1994/95, I wrote an API, in C, that was a communication interface. We had to use C, because it was the only language that had binary/link compatibility between compilers (the ones that we used).

We designed what I call "false object pattern." It used a C struct to simulate a dynamic object, complete with function pointers (that could be replaced, in implementation), and that gave us a "sorta/kinda" vtable.

Worked a charm. They were still using it, 25 years later.

That said, I don't really miss working at that level. I have been writing almost exclusively in Swift, since 2014.


> We designed what I call "false object pattern." It used a C struct to simulate a dynamic object, complete with function pointers (that could be replaced, in implementation), and that gave us a "sorta/kinda" vtable.

You were not alone in this. It is the basis of glib's GObject which is at the bottom of the stack for all of GTK and GNOME.


sadly glib doesn't have error handling for memory errors,it will just crash. otherwise it can be widely used as a oop c


I got the idea from Apple's QuickDraw GX. It used a similar pattern.


Sure, that's a pretty common pattern in use in C to this day. It's a useful pattern, but it's still all manual. Forget to fill in a function pointer in a struct? Crash. At least with C++ it will fail to compile if you don't implement a method that you have to implement.


At least in C it's only plain old function pointers.

You don't have to think about exceptions, overloaded operators, copy constructors, move semantics etc.


You still need to think about error handling, and it's not standardized because everyone else will also have to think about it ad hoc.

You'll also still need to think about when to copy and move ownership, only without a type system to help you tell which is which, and good luck ensuring resources are disposed correctly (and only once) when you can't even represent scoped objects. `goto` is still the best way to deal with destructors, and it still takes a lot of boilerplate.


But you have a choice, you don't have to implement all of C++/Rust.

The beauty of C is that it allows you to pick your level of complexity.


You don't have to use the entirety of C++, either.


No, but that tends to gradually happen all by itself from my experience, even in solo projects. Because it feels stupid writing something yourself when you have access to stdlib. Also, I feel rolling your own stuff in C++ is a major pita in comparison to C, too many rules to memorize for me.


> still all manual

That pretty much is the definition of C. It was designed to be a system language, that was "one step beyond" (my own Madness reference) the assembler.

It's a dangerous tool, and should be wielded by experts. The same goes for all kinds of similar stuff.

And "experts" is kind of a darwinian thing. There's not really that many folks that can successfully wield it in large quantities (a certain cranky Finn comes to mind). There's a ton of folks that are surfing the Dunning-Kruger wave, that think they are up to it, but they tend to face-plant.

For myself, I learned to do it right, but it was difficult, stressful work, and I don't miss it. Working in Swift is a joy. I don't have to worry about things like deallocations, and whatnot.


I've never met an expert who writes c well all of the time, so I have been led to believe it's simply not possible. Folks with 20+ years of experience will still regularly write a use after free but which is not caught until it hits production. I'm not sure I've actually seen C code free of concurrency bugs. If it's possible it takes years of battle hardening with no feature work. Humans are prone to make mistakes and we need to use as many tools as possible to help us avoid making those mistakes. They are recently looking into introducing thread annotations for locks in the Linux kernel and I expect them to find lots of real bugs lurking in otherwise well maintained subsystems. I've rewritten swaths of code into rust and always seen it find a bug in the original code's design that we were lucky to never run into.


haha every segfault puts me back down into the valley of despair xD. thats totally ok!

higher level languages often make it so you can focus on the language mostly. C and assembly, i think, you need a lot of context. what is running the code, and how.


Of course you can. It's quite the opposite actually. The downside is that in C you have to code a bunch of abstractions _yourself_. See how large projects like the Linux kernel make extensive use of macros to implement an object system.


so confused by this one. C is the most free to build systems and abstractions. it doesnt lock you down into any paradigm so you can build your own....

you can abstract away perfectly fine low level details and use your own high level constructs to build what you want in a memory safe way...

high level daya objects?

sure it doesnt have garbage collection, so it motivates you not to leave garbage laying around for some slow background process to collect.

actually you can build this in C you just do not want to...

you can give all your objects reference counters and build abstractions to use that, you can implement 'smart pointers' if u want, and have threads do garbage collection if u want... why not? what exactly is stopping you?

maybe its less convenient to go that route. but impossible? nope.

connect GDB to your c++ program and see how it works... its not like c++ suddenly doesnt become machine code. and c translates perfectly fine to machine code...


Sure you can do this, but the point is that it's all manual and always will be because you get no help from the compiler.

Compare in C++ where you can have higher level types and compiler helpfully provides features that let the programmer do stuff such as RAII or unique_ptr.

Huge difference.


Thank you for your work on XFCE. It's been the best WM/UI for me for over a decade.


> Memory leaks, NULL pointer dereferences, use-after-free

I suffered writing those for many years. I finally simply learned not to do them anymore. Sort of like there's a grain of sand on the bottom of my foot and the skin just sort of entombed it in a callous.


I've seen you make these kinds of comments before on other articles. Please stop. Not everyone is perfect and can forevermore avoid making any mistakes. I strongly suspect your opinion of your skill here is overinflated. Even if it isn't, and you really are that good, everyone cannot be in the top 0.00001% of all programmers out there, so your suggestion to "simply" learn not to make mistakes is useless.

This all just comes off incredibly arrogant, if I'm being honest.


I think a more charitable interpretation of what he said was, "after sufficient practice, I became good enough to start avoiding those pitfalls."

It's not all that different from learning any challenging task. I can't skateboard to save my life, but the fact that people can do it well is both admirable and the result of hundreds or thousands of hours of practice.

Skilled people can sometimes forget how long it took to learn their talent, and can occasionally come off as though the act is easy as a result. Don't take it too harshly.


> after sufficient practice, I became good enough to start avoiding those pitfalls.

You can only do that in code you control. You can not control the entire call stack of your functions in cooperative environments.


> You can not control the entire call stack of your functions in cooperative environments.

That's true. But I try by doing code reviews, and I try to lead the team by example and suggestion.


Examples and suggestions don't transmit your years of experience of pitfall-avoidance. You say you "try" to do code reviews, which is honorable, but also proves that there is code in your codebase that you can not and do not control, and which likely features unsafeness. This is fair enough, we can't do all the work as maintainers after all.

When I `git blame` segfaults in the D compiler repository, the aforementioned assumption -- that you can't control faults introduced by someone else -- seems to be largely true. We do use Go/Rust at work (albeit an easier flavor of it), because I don't trust my juniors.


And, given who OP is, "sufficient practice" here translates to "has been writing compilers in C++ for 4 decades".


> This all just comes off incredibly arrogant

I know, but it's the truth. Consider another case. I worked in a machine shop in college trying to make my steam engine. The man who ran the shop, was kind enough to show me how to operate the machines.

Those machines were like C, built during WW2. They had no user friendly features. The milling machine was a particular nightmare. It was festooned with levers and gears and wheels to control every aspect. There was no logic to the "user interface". Nothing was labeled. The worst one was a random lever that would reverse the operation of all the other levers. My terror was wrecking the machine by feeding the tool mount into the cutting bit.

I would make a part, and it would come out awful - things like the surface finish was a mess. If he had some time, he'd come over and help me. He'd turn a wheel, "ting" the blade on a grinding wheel, adjust the feed, and make a perfect part (all by eyeball, I might add). The man was just incredible with those machines. I was just in awe. He never made a mistake. Have you ever tried to get a part centered properly in a 4-jaw chuck? Not me. He could on the first try every time.

But he'd been doing it every day for 40 years.


He's not making a comment about everyone, it's a specific comment about how often long time C programmers make basic mistakes after a million SLOC or so.

In this instance Walter is correct - the mistakes he listed are very rarely made by experienced C programmers, just as ballet dancers rarely trip over their own feet walking down a pavement.

The problem of those errors being commonplace in those that are barely five years in to C coding and still have another five to go before hitting the ten year mark still exists, of course.

But it's a fair point that given enough practice and pain those mistakes go away.


> ballet dancers

So true. I know dancers who have been at it for decades. They "make it look easy", and it is easy for them. But try to do it yourself, and you look like a moose.

A friend of mine trains dancers for a living. He says the most effective teaching tool is the video camera. But he doesn't bring it out until his student is "hooked" on dancing, because prematurely showing them the video of them dancing just crushes them.

P.S. You can tell a ballet dancer just by the way they walk down the street. I once was at the airport and saw a lady unloading her bags. I just said Hi, you must be a ballet dancer! She replied nope, I'm an ice dancer, off to a competition. LOL.


I'd imagine the main way one reduces instances of these mistakes is to restrict resource ownership into certain patterns which have a clear place for freeing, and rules that ensure it's always reached, and only once.


There are many approaches depending on the type of program or suite of programs being built.

Always pairing the creation of free() code and functions with every malloc() is one discipline.

Another, for a class of C utilities, is to never free() at all .. "compute anticipated resource limits early, malloc and open pipes in advance, process data stream and exit when done" works for a body of cases.

In large C projects of times past it's often the case that resource management, string handling, etc are isolated and handled in dedicated sub sections that resemble the kinds of safe handling methods baked into modern 'safe' languges.


Right. Another one, small but effective, is to put an overflow check on the calculation of the size to pass to malloc().

And another - always use size_t for anything that is used as an index.


> just as ballet dancers rarely trip over their own feet walking down a pavement

What about about walking down a busy construction site? The most charitable and correct interpretation I can think of is "I'm a professional. Seatbelts and OSHA destroy my productivity."


> What about about walking down a busy construction site?

Coordinated people with some years of experience pay attention to the ground and overhead cranes and conveyor belts and survive walking through construction sites, mine sites, aviation hangers, cattle yards, musters, et al on a routine basis. I'm 60+ and have somehow navigated all those environs - including C for critical system control.

These are dangerous environments. No one denies this. It's still true that the longer you inhabit such spaces the safer your innate learned behaviour is.

C has seatbelts and OSHA - valgrind, et al tools abound for sanity checking.

Walter's GP statement is literally little more than "eventually you grow out of making the simple basic maistakes" - eventually, after some years of practice - which is a real problem with C, it takes time to not make the basic mistakes. After all that, there's always room, in C, in Rust, whatever, to make non basic non obvious mistakes.


> After all that, there's always room, in C, in Rust, whatever, to make non basic non obvious mistakes.

Correct, I guess. The number of relatively obvious mistakes should decrease with experience. And it stands to reason that eventually it settles near zero for some part of developer community.

How close to zero and which part of community? Statistic is scarce.

> C has seatbelts and OSHA - valgrind, et al tools abound for sanity checking.

Optional tools with no general enforcement. That is more like elective vaccination or travel advisories. That is, no, no seatbelts and no OSHA.


> The number of relatively obvious mistakes should decrease with experience

Yes, and the kind of mistakes I make have changed. Now they are usually a failure to understand the problem correctly, or are simply typos.

> Optional tools with no general enforcement

That's right. The tools don't work unless you use them. With D the idea is to build the tool into the language itself. I once annoyed the Coverity folks by saying D's purpose is to put Coverity out of business.


> Seatbelts and OSHA destroy my productivity

D is designed with seatbelts (like array overflow protection), and they work. I regularly show how C could add them with only minor additions.


You come off as incredibly arrogant too, you just don't realise it because you have the current mainstream opinion and the safety of a crowd.

Do you know how fucking obnoxious it is when 200 people like you come into every thread to tell 10 C or Javascript developers that they can't be trusted with the languages and environments they've been using for decades? There are MILLIONS of successful projects across those two languages, far more than Rust or Typescript. Get a fucking grip.


Nobody is telling JS developers that Rust will save them, chill.


Well, not 100% true... With Rust being the principal language that compiles to webassembly, I'm sure there is quite a lot of JS code being re-written in Rust right now.


But you'll be called things like "catastrophically unprofessional" [1] if you choose JavaScript over TypeScript, or dynamically-typed Python over MyPy.

[1] https://www.reddit.com/r/Python/comments/1iqytkf/python_type...


He's right. That is catastrophically unprofessional in a professional environment.


Surely it is better than yet another self-promoting mention of his programming language on every unrelated C, Rust or Zig post?


Every post on D attracts C, Rust, and Zig replies :-)

Feel free to downvote any and all posts I make.


This is actually quite easy to achieve, as long as you cannot realize your own mistakes.


Have you looked at Zig? It is often termed a modern C where Rust is the modern C++. Seems like a good fit.


i never really understand why these get compared. i wouldn't expect that much overlap in the audiences

zig seems like someone wanted something between C and "the good parts" of C++, with the generations of cruft scrubbed out

rust seems like someone wanted a haskell-flavoured replacement for C++, and memory-safety

i would expect "zig for C++" to look more like D or Carbon than rust. and i'd expect "rust for C" to have memory safety and regions, and probably steal a few ocaml features


The single best feature (and I would say the _core_ feature separating it from C) that C++ has to offer is RAII and zig does not have that. So I don’t know which good parts of C++ they kept. Zig is more of its own thing, and they take from wherever they like, not just C++.


Zig has defer, which is arguably way simpler. Is there something RAII can do that defer can't?

Not everyone likes RAII by itself. Allocating and deallocating things one at a time is not always efficient. That is not the only way to use RAII but it's the most prevalent way.


defer can't emulate the destructors of objects that outlives their lexical scope. Return values and heap objects are examples of these since they outlive the function they were created in. defer only supports enqueuing actions at lexical boundaries.

If you destroy an object that outlives the lexical scope it was created in, then you have to clean up manually.


Defer can be omitted


> So I don’t know which good parts of C++ they kept.

comptime is a better version of C++ templates.


I really wish that were true but it isn’t. Modern C++ templates/constexpr are much more powerful and expressive than any Zig comptime equivalent.

The power and expressiveness of the C++ compile-time capabilities are the one thing I strongly miss when using other languages. The amount of safety and conciseness those features enable makes not having them feel like a giant step backward. Honestly, if another systems language had something of similar capability I’d consider switching.


I have written a lot of Zig comptime code and ended up finding the opposite. In C++ I find I have to bend over backward to get what I want done, often resulting in insane compile times. I've used metaprogramming libraries like Boost Hana before to have some more ergonomics, but even that I would consider inferior to comptime.

Out of curiosity, do you happen to have any examples of what you describe, where C++ is more powerful and expressive than Zig?


If it looks anything like what I read in "Modern C++ Design" 20+ years ago then I'll pass. That book made me realize the language wasn't for me anymore.


It looks nothing like C++ decades ago, it is effectively a completely different language. I found C++ unusable before C++11, and even C++11 feels archaic these days. Idiomatic C++20 and later is almost a decent language.


"Modern C++ Design" was the first book that highlighted template metaprogramming and showed you ways to use it as a Turing complete programming language in itself. Even a decade+ after it was printed my friend said it was recommended reading for employees within Google so I gave him mine - but I'm not sure about these days.

Talking about how much you can do with C++ templates made me think of that.


It’s not like that anymore.


I would say OCaml more than Haskell, but yes.


I have, and I do find Zig impressive, but it doesn't go far enough for me. I don't want a "better C", I want a better systems language that can also scale up for other uses.

I like strong, featureful type systems and functional programming; Zig doesn't really fit the bill for me there. Rust is missing a few things I want (like higher-kinded types; GATs don't go far enough for me), but it's incredible how they've managed to build so many zero- and low-cost abstractions and make Rust feel like quite a high-level language sometimes.


Almost everyone wants to claim to be modern c and not modern C++ because everyone shit on C++

If anyone is the modern c++ its D. They have template metaprogramming while rust has generics and macros.

Rust doesn't have constructors or classes or overloading. I believe its type system is based on Hindley–Milner like ML or Haskell and traits are similar to Haskell type classes. Enums are like tagged Union/sum types in functional programming and rust uses Error/Option types like them. I believe rust macros were inspired by Scheme. And finally a lot of what makes it unique was inspired by Cyclone (an obscure excitemenal language that tried to be a safer C) and other obscure research languages.

I guess rust has RAII that's one major similar to c++. An there's probably some similarities in low level memory access abstraction patterns.

But I'd describe rust as more of an imperative non GC offshoot of MLs then a modern c++ evolution.


Rust is not a modern C++, their core models are pretty different. Both Rust and C++ can do things the other can’t do. C++ is a more focused on low-level hyper-optimized systems programming, Rust is a bit higher level and has stronger guardrails but with performance closer to a classic systems language.

I do think Zig is a worthy successor to C and isn’t trying to be C++. I programmed in C for a long time and Zig has a long list of sensible features I wish C had back then. If C had been like Zig I might never have left C.


What do you consider the difference in their core models.

Rust and C++ both use RAII, both have a strong emphasis on type safety, Rust just takes that to the extreme.

I would like to even hope both believe in 0 cost abstractions, which contrary to popular belief isn't no cost, but no cost over doing the same thing yourself.

In many cases it's not even 0 cost, it's negative cost since using declarative programming can allow the compiler to optimise in ways you don't know about.


Rust is basically what you get if you start with ML and then do enough to avoid having garbage collection.


This seems not correct at all. There are very useful ML patterns that don't exist at all.

Rust feels like taking an imperative language and adding ML features till you cannot anymore.


C++ is (according to Bjarne Stroustrup) a general purpose programming language that can be used to build general business software and applications with, not just a systems PL. This is why perf is all over the place —- the core language is fast like C but the stdlib contains terribly slow code (regex, exceptions) and ways to waste lots of cycles (wrapping everything in smart pointers, using std::map instead of unordered map).


What's slow about regex? A properly optimised regex engine ought to be very efficient (if used sensibly, at least).


std::regex in C++ cannot be "properly optimized" due to various factors: https://stackoverflow.com/questions/70583395/why-is-stdregex...


The answer there [0] doesn't support this. From the answer:

> There is no question that libstdc++'s implementation of <regex> is not well optimized. But there is more to it than that. It's not that the standard requirements inhibit optimizations so much as the standard requirements inhibit changes.

The answer's one comment expands on this, it sounds like they're not able to add a sophisticated optimising regex engine into the libstd++ shared library (i.e. non-inline) as this would be an ABI break.

Perhaps other implementations of the C++ standard library perform better.

[0] https://stackoverflow.com/a/70587711/


In my understanding, other implementations cannot perform better, because the root cause is how it is defined in the standard. Basically, any better implementation would be non-conforming. It’s not the kind of ABI issue where simply choosing a different one would help; the flaw comes directly from the definition, which cannot be changed.

Of course, non-std implementations of regexes in C++ don’t have this issue. Were strictly talking about the standard library one.


That is very interesting. You have quite the resume too. While I've dabbled in nearly everything, I'm a day to day pro C# developer and I absolutely love it. I've never been upset or had a complaint. If I were forced off for some reason, I'd just go to Typescript. I can't imagine using C. Perhaps with some form of AI valgrind. The problems C solved are just not relevant any longer, and it remains entrenched in 2025. Rust with AI analysis will be amazing to see the results of.


It looks to me like C is still very relevant, and isn't going anywhere anytime soon.

I realize a lot of people don't want to use it; and that's fine, don't use it.


It depends on problem domain. If you're writing kernel level code or device drivers you wouldn't use C# or typescript.


There are at least a few kernels, bootloaders, and device drivers written in C# out there… granted for hobby/research.

https://github.com/Michael-K-GH/RoseOS

https://vollragm.github.io/posts/kernelsharp/

https://www.gocosmos.org

https://www.microsoft.com/en-us/research/project/singularity... (asm, C, C++, largely C# (Sing#))


I will say the more recent additions to C++ at least have solved many of my long standing issues with that C-variant. Most of it was stuff that was long overdue. Like string formatting or a thread safe println. But even some of the stuff I didn’t think I would love has been amazing. Modules. Modules bro. Game changer. I’m all in. Honestly C++ is my go to for anything that isn’t just throw away again. Python will always be king of the single use scripts.


The problem is that they are _additions_, C++ has such absurd sprawl. The interactions between everything in this massive sprawl is quite difficult to grasp


That's also a problem in C land, of course, perhaps with less total sprawl.

Yeah, it has new features, but you're stuck working on a C89 codebase, good luck!

I don't know a great answer to that. I almost feel like languages should cut and run at some point and become a new thing.


Perhaps less? More like certainly a ton less. Regards, someone who uses C++ and doesn't even hate it.


I lost interest in keeping up with C++'s advances more than a decade ago.

The problem is that I want a language where things are safe by default. Many of the newer stuff added in C++ makes things safe, perhaps even to the level of Rust's guarantees -- but that's only if you use only these new things, and never -- even by accident -- use any of the older patterns.

I'd rather just learn a language without all that baggage.


Similarly, I went from writing a lot of C to Python, and I appreciate both of them for almost opposite reasons. I ended up finding I like Cython quite a bit, even though the syntax leaves much to be desired. The power, performance, and flexibility of C combined with the batteries included nature of Python is a match made in heaven.

You're also still very much free to write either language purely, and "glue" them together easily using Cython.


Lately I've been paying interest in D being from PERL/TCL background. Rust, Python, Go just feel tainted to me.

https://dlang.org


Dlang is a clean langauge. Unfortunately it never got the attention it deserves.


But TFA is not writing in C. TFA is writing in a language that compiles to C.


I Really want to use Rust. But Rust has so many std functions that kind of abstracting how does it work under the hood

For Ex: I still has no idea what clone() does, how does it interact with memory, on heap or stack , does it create a new instance, or just modify metadata of that object. Sometime creating a new instance is a big no-no because it takes a lot of memory.

Same thing with "ownership transfer", is variable freed at that moment, etc.

I bet i could find answers on internet, but rust has like 500 std functions, so the task is tedious


What is your current language of choice, both C and C++ have the same problems as what you just described.

Regarding ownership transfer it is even worse in C, what if you forget, after moving an object out of a variable, to set that variable to NULL, then free that variable, that's a use after free. At least in C++ you have move semantics although it is still error prone. In rust it's a compiler error.

Copy and Clone is the same, and both are opt-in for your own types, by default the only options are move or reference for your own types, in C and C++ by default it is copy, which again leads to use after free in the situations you complained about.

I feel if these are your complaints you will actually benefit from spending some time in Rust.

If your preferred language is higher level languages with GC that is reference by default I encourage you to try any of the systems level programming languages, the things you complain about are things that are important for a language to have constructs for, reference semabtics by default causes many issues, and becomes untenable in the parallel world we live in.


Well, i dont use C++ much(i'm FW engineer, most of my stuff is in C).

The std in C is simple and explicit. For Ex: I can make an educated guess how memcpy() work by looking at its signature. It takes pointer to src and destination, and size, so i can guess it does not allocate any new memory(or if it has, it has to be some kind of optimization reason).

Another example is strstr(), it returns pointer to a piece of memory i provided to it, so i can safely do some pointer math with the return value.

It's true that i do not spend much time in Rust, so maybe i'm missing some fundamental things. I guess my mistake is trying to apply my knowledge in C to rust.

But still, it's kind of irritating now knowing (or guessing) how does function work just by looking at at its signature.


You can surmise the behavior from the signature of clone in the same way that you can memcpy(), it returns an owned type so it will allocate. If you want to match the signature of memcpy then you don't want the allocation and you can instead call clone_from() which takes a mutable borrow of something you had to allocate.

> piece of memory i provided to it, so i can safely do some pointer math with the return value.

Except pointer math is never going to be safe in C, a particular case might be bugfree but it is not safe. Moreover, nothing in the C language says that the provenance of the output pointer matches the provenance of the input pointer (it could take a reference to a pointer from some other thread while that other thread is freeing that memory). In rust you will pass a pointer and get a pointer back with the same lifetime, then you can safely use the pointer in that lifetime bound: the provenance is part of the signature. So in this case, you are incorrectly assuming things from the C signature while the corresponding rust signature definitively tells you.

So yeah, if you learn more about rust then you will see that in fact it tells you more than the corresponding C signatures.


Isn't guessing what a function does based purely on its name pretty risky? There's usually nuance, so if I'm not already familiar with a function, I'm looking up its docs at least once.


> It takes pointer to src and destination, and size, so i can guess it does not allocate any new memory

Does the pointer provided by src get altered in any way? Might is be NULL after calling memcpy? What happens if I pass NULL to dst? Is size in bytes or whatever the pointer is pointing to?

The moment you need to read a man page to get any of those mab pages you can read the docs for clone and get all the information you would need.

> Another example is strstr(), it returns pointer to a piece of memory i provided to it

This is not at all clear from the signature, from the signature it might allocate and return a new string entirely that you would need the deallocate, the only way to know that is to read the docs which runs into the problem again.

And again there is no indications that the pointers passed into the function are not mutated in any way other than convention and documentation.

Rust makes all of these things explicit with a compile error if you fail an invariant of the type systems.

Btw it's possible to encode the same invariants in C++ but isn't the default most of the time.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: