If you’ve ever actually written more than toy C, you’ll know gotos are essential for resource cleanup and error handling. Even the famous goto fail was not a goto error, it was a block error (named because a cleanup statement `goto fail;` always executed because of a brace-less if). goto can be abused like any other language construct, but it’s uniquely useful and makes code more simple and easy to reason about when used correctly. You just shouldn’t be using C any more unless under duress.
Not really, I have deployed plenty of C code into production and never used gotos, other than special flavoured gotos like UNIX signals.
My problems have always been in how memory corruption friendly C happens to be, nothing to do with how to use structured programming practices for resource cleanup.
I don't see any similarity between gotos and signals at all. Gotos are just explicit jumps, while the "normal" structured way of doing jumps is to have them inferred from nested regions (that double as scopes and lifetime guards for automatic variables).
The structuredness of such inferred jumps usually is both sufficient and convenient, but sometimes being extremely structured leads to boilerplate and inefficiencies. That shows already with early returns from nested scopes, which aren't widely frowned upon, while they are very similar to common usage of goto. I would say I haven't encountered a goto that isn't basically an early return from some nested scope to after some parent or grand-parent scope.
In that sense, a goto can save from having to extract a nested scope as a stand-alone function just to write it as an early return. I would say that many gotos in the wild could be rewritten as early returns after making a standalone function, but maybe sometimes this is too much of a hassle, or, subjectively, puts a toll on readability.
Signals are implicit GOTOs in the sense of INTERCAL's COMEFROM.
As for structured handling, instead of gotos all over the place, do inverted conditions for early returns similar to what Swift has done with guard statement, embrace functions for resource cleanup, if the cost of a jump brings cold sweat, have them inline and called alongside a return, most compilers will replace calls with jmp opcodes.
> Signals are implicit GOTOs in the sense of INTERCAL's COMEFROM.
What?
> guard
How does it help to have an inverted if statement? Not sure what is the point, can do without.
Goto isn't necessarily for resource cleanup. The common usage is as an early break from the currently executed block, to what comes after the block. Which is often cleanup code, but not necessarily.
You clearly don’t understand the problem. Imagine all the ifs properly inverted like you want them to be. Good. Now imagine you have to free 6 allocations, close 3 FDs, and wait on a few threads before returning. Imagine you have 10 early returns. Your cleanup function would be an unreadably silly 10 arg thing with extra pointers everywhere and you have to call it 10 times. Thats insane boilerplate just because you cant stomach a local goto. Why not just goto cleanup, avoid the mess, save a bunch of time, and cleanup naturally and locally in the same function where everything is defined?
If you're steeped in C and its quirks, have good coding patterns that allow for it, I say knock yourself out.
I'm absolutely coding in C again — writing some old-school games using SDL. After working for decades with various retain/release, garbage-collected, magic-memory™ languages, going back to C feels like programming again. I like it.
> If you're steeped in C and its quirks, have good coding patterns that allow for it, I say knock yourself out.
Somewhere between 95% and 100% of people who think they're "steeped in C and its quirks, have good coding patterns that allow for it" write code that invokes undefined behaviour.
Undefined behavior means the language provides no guarantees about the outcome. Whatever happens is up to the compiler, or rather the specific implementation of the compiler, and the target architecture on which the program is run. These things are not constrained by any spec or guarantee, so they are allowed to change, for any reason, to anything. These changes may be triggered by not only different execution environments, but also changes to properties of a given execution environment. The behavior of a program with undefined behavior is non-deterministic, and cannot be predicted, modeled, or effectively maintained.
Like I said, Rust and Python have no specification at all, so technically any program written in them is undefined behavior, depending on the particular interpreter/compiler binaries you use and your architecture.
The only sin of C++ is the same that plagues evolutionary languages like Typescript, Kotlin and so forth.
No matter how many tools they provide to write better code than the language they have grown from, their compatibility with them is like hearding cats while trying to have everyone adopt best practices.
Otherwise in regards to Arduino, I would suggest a couple of nice BASIC and Pascal compilers like those sold by Mikroe.
The ATmega 328P an Arduino Uno or Nano uses has 2KB of memory and 32KB of flash storage for the program. Having some sort of micropython interpreter there would be impossible, and even if it could be achieved, somone still has to write the low level C/ASM code to make it all work. For many embedded systems, low level languages like C or C++ (perhaps Rust for a lot of ARM micros) is the only sensible choice. If it's not a multi user system that's connected to a network, people trying to abuse memory unsafe code isn't a concern.
> has to write the low level C/ASM code to make it all work
shhh. Let's not disturb those that believe in the GC fairy that sprinkles their code with safety magic late at night while they soundly sleep. Firmware doesn't exist. I can't hear you. Na na na na na na na
I hope Rust takes off for embedded programming. I've been working with ESP32 and C is really the only choice if you are doing anything remotely fancy or resource constrained
What bothers me about rust is the designers are people that have been bitten hard by working on browsers written in C++ with it's manual memory management and no way to enforce object lifetimes. Rusts solution to that seems like a big hammer when it comes to embedded where you usually have either stack allocated or compile time allocated objects.
If you code in that style you won't even notice the borrow-checker is there. Unless you have a bunch of shared global state, the the overhead of arc will seem silly on your single core uc.
That kind of style is often correlated with a lot of shared global state, even in relatively high level software like database engines. Most software avoids shared global state by delegating that implementation to the operating system, which often comes at the cost of performance.
I have worked in embedded code bases like that. While some of it is unavoidable for io, I have worked in other codebases where the compile time allocated memory was moved around in a way that would have satisfied the borrow checker except for that first mutable borrow. I haven't done rust for uc yet, so I won't claim it is a good fit, but it didn't seem like the parent poster had either, and I think there is a decent chance it could work well of the tooling is there. Which I'm not sure it is.
Python is more than adequate for most of the simple scripts that people run on Arduinos. There's no reason that a lightweight interpreter or even a compiled implementation could not run on even the AVR-based Arduinos, and obviously the beefier ones are straight up 32-bit ARM so it would be trivial to stand up MicroPython on them.