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

I searched for conditions and restarts. I did not find them.

That makes me sad because that is what my systems programming language uses.

For context, conditions and restarts come from the Lisp world. They do not unwind the stack. Instead, they run an error handler on error, and if it handles the error, you have the option of restarting the operation that failed.

I have implemented them in C; they are great for systems programming.



FWIW, Windows structured exception handling uses this model. When an exception occurs your handler is called and it chooses whether to continue execution or to continue by unwinding the stack.


You can, of course, do the equivalent with explicit callbacks and a bit of additional boilerplate. However, I have never felt the need to do that (as opposed to, say, having a numRetries parameter), so I always wonder how people who advocate for such a mechanism design and modularize their code. It feels like it leads to dependencies on implementation details, as opposed to only relying on a procedure’s interface.


The errors that a procedure can run into are part of its interface. Languages like Zig and Swift make that explicit.

The difference is that callers (even indirect callers) can register handlers to handle those errors without unwinding the stack.

Maybe the error handler makes it return a default value. Maybe it will deallocate a cache to free up memory in a memory-constrained situation. Maybe it purely ignores EOF or a FILE DOES NOT EXIST error where those do not matter.

I also have a special error handler for parsing. If the error is a parse error, it prints the parse error in standard format, with source file, line, etc. Otherwise, it passes the error on to the next error handler in the chain.

This helps me modularize my code by keeping the parse error concerns separate from error handling for everything else.


20 years go while programming java, I was struck by how simple and universally useful it is, to have any method that throws exceptions to be forced to declare them in the method signature, and all callers are then forced to either pass on the exception or provide a try catch that deals with it, making it almost impossible to have programd crash from un handled exceptions.

It was perfect, except for all the crashes from null variables back then. And my current favourite language Dart cuts null pointers by 99.99 percent by giving you null safety.

Checked exceptions and null safety, and suddenly programs crash like 90 percent less.

Now we must just figure out how to systematically eliminate index-out-of-bounds too, then 98 percent of program crashes are gone. All without writing unit tests and other costly rituals.

Just good language design.


The worry I have with code like that is that instead of letting the program crash it often just swallows exceptions. This can lead to a lot of silent errors that one becomes aware of way too late, for example after a lot of data has been corrupted for hours or years.


Hi Gavin, I've seen your blog before, including some posts about Yao.

After feedback on Lobste.rs, I plan on adding a section on conditions and restarts, hopefully sometime later today if time permits. :)

I'd be happy to add some information about Yao as well alongside Common Lisp. It would be helpful for me to have some more details about Yao before writing about it, so I have some follow-up questions below. Please feel free to link me to existing writing; I may be misremembering details, but I don't think the answers to these have been covered somewhere.

I looked at the docs on the master branch as of mid Jan 2025 (could you confirm if these are up-to-date), particularly design.md, and I noticed these points:

> That means that Yao will have unsafe code, like Rust's unsafe. However, unlike Rust, Yao's way of doing unsafe code will be harder to use,

So Yao has a delineation between safe and unsafe code, correct? Does "safe" in Yao have the same (or stronger) set of guarantees as Rust (i.e. no memory safety if all necessary invariants are upheld by unsafe code, and boundary of safe-unsafe code)?

> Yao's memory management will be like C++'s RAII [..]

Does Yao guarantee memory safety in the absence of unsafe blocks, and does the condition+restart system in Yao fall under the safe subset of the language? If so, I'm curious how lifetimes/ownership/regions are represented at runtime (if they are), and how they interact with restarts. Specifically:

1. Are the types of restart functions passed down to functions that are called? 2. If the type information is not passed, and conditions+restarts are part of the safe subset of Yao, then how is resumption logic checked for type safety and lifetime safety? Can resumptions cause run-time type errors and/or memory unsafety, e.g. by escaping a value beyond its intended lifetime?

---

For reading the docs, I used this archive.org link (I saw you're aware of the Gitea instance being down in another comment): https://web.archive.org/web/20250114231213/https://git.yzena...


Hey! Good to talk to you.

Sorry, I was AFK for several hours.

Thank you for the offer, but I don't think Yao should be featured yet.

Edit: I guess I'll answer your questions anyway.

> So Yao has a delineation between safe and unsafe code, correct?

Correct. However, Yao's "unsafe" will actually be separate files, written directly in an LLVM-like assembly (Yvm in the repo). That's how it will be harder to use.

> Does "safe" in Yao have the same (or stronger) set of guarantees as Rust (i.e. no memory safety if all necessary invariants are upheld by unsafe code, and boundary of safe-unsafe code)?

Stronger.

First, Yvm (Yao's assembly/unsafe) is made specifically for structured languages, and it will still do bounds checks by default. Yes, there will be ways of not doing bounds checks, of course, but even in "unsafe," bounds checks will exist.

Second, Yao and Yvm are both explicitly designed for better formal verification. [1] This includes user-defined formal properties.

> Does Yao guarantee memory safety in the absence of unsafe blocks?

Yes.

> does the condition+restart system in Yao fall under the safe subset of the language?

It's still being implemented (hence why Yao should not be featured), but it will be a part of the safe subset. That is a guarantee; I will completely redesign Yao if I cannot fit conditions and restarts in the safe subset. But I am confident that they will work as-is because I implemented a prototype in C that is safer than C itself.

> 1. Are the types of restart functions passed down to functions that are called?

Not directly. My C code uses, and Yao will use, what I call "context stacks," an idea that comes from Jonathan Blow's Jai.

These are more useful than just restart functions, but there is explicitly one context stack for restart functions, per thread. Registering a restart function means pushing it onto the context stack.

Then, when an error happens, the context stack is walked backwards until a restart function handles the error. If no function handles it, the context stack for the parent thread is walked (starting at the point where the child thread was created), and so on until some function handles it.

I push a default restart function at the root, so errors will always be handled.

> 2. If the type information is not passed, and conditions+restarts are part of the safe subset of Yao, then how is resumption logic checked for type safety and lifetime safety? Can resumptions cause run-time type errors and/or memory unsafety, e.g. by escaping a value beyond its intended lifetime?

This is one of the craziest parts of Yao: it will have the capability to be generic over types at runtime. In addition, Yao has something like Go interfaces or Rust traits. What it has is more powerful, though.

The end result is that errors will actually be interfaces, and everything will be type checked at comptime, but be memory safe.

I hope this answers your questions.

[1]: https://gavinhoward.com/2024/05/what-rust-got-wrong-on-forma...


I have read about the CL condition system. I get how restarts are useful for interactive programming.

I don't see how they could be used in most cases where you want a program to run without a programmer intervening. Could you list some more usecases.


Restarts don't always need a debugger or intervention.

A classic case would be restarting memory allocation after failure and after the error handler freed cache memory.

Another case would be retrying to open a file when the process has too many files open already. The error handler may close a few that are not critical, then have the restart fire.

Another case is sending something through TCP. Perhaps you try to send something, and it gives you an error. Unbeknownst to you, the message was already sent, but you wait a second or until other connections do less, then restart and try again, and it succeeds. The other end gets a duplicate, but no matter; it's TCP.

Another case is DNS. Say you need to get the IP address for some URL, and you connect to your first default DNS server. However, it happens to be run by your local incompetent sysadmins, and it happens to be down. Your error handler may choose a different, maybe public, DNS server, like Cloudflare or Google, and then restart.

If you think, 'Oh, well, I could program those in without restarts,' you are correct, but the thing is that doing so couples things.

Take the DNS example: if you put that extra error handling logic in the code that actually tries resolving things, then how do you change error handling when you need to?

Let's make the example even more detailed: perhaps you have a fleet of servers, a whole data center. Most of those servers could use a public DNS if they needed to, but perhaps your head node must NEVER use a public DNS for security reasons. The typical way to implement that would mean having an `if` statement for acting differently based on whatever condition would indicate head node or not. That is coupling the DNS resolution with error handling.

But if you have conditions and restarts, then you simply register a different DNS error handler at startup based on if it's the head node or not. Or the error handler could have the `if` statement instead. Either way would decouple DNS resolution from the error handling.

I hope all of that helps.


That does help. Network stuff is a usecase I could see.

It seems like with a restart system you could do something like this, in a generic reusable library way, correct me if I'm wrong. On network failure, register a callback with some OS level network activity watcher, once the network has resumed working, continue execution as normal.


You could do that, except that you would register the handler before the network code executes. Then, on failure, the network code would run the handler that waits until activity resumes, then restarts.


Did you publish your implementation anywhere? Would be very interesting to see. Thanks!


I did, but my Gitea instance stopped working. I am trying to replace it.


That's just an overly fancy version of ON ERROR RESUME NEXT.


No, it is much more powerful.

ON ERROR RESUME NEXT is equivalent to a catch all in a try catch. Conditions and restarts let calling code set up error handlers, and if one of them handles the error, it would be like starting over from the top of the try block.




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

Search: