[Analysis / Pre-RFC] Variadic generics in Rust

How would that work? To recap: The problem with computing directly in the type system is that it's a Turing tarpit. The solution is to use normal Rust code instead, which we're already getting to some extent in the form of const generics (even if they can't currently compute with types). But Rust code can panic or infinitely loop. So are you suggesting some sort of termination checker for const expressions that enforces primitive recursion? Or are you suggesting that the type system would somehow be extended to not be a Turing tarpit, without using normal Rust code?

If it's the latter, I don't see how primitive recursion has much to do with it.

If it's the former, a checker for const expressions… well, before worrying about termination, let's start with panics. Right now, with const generics, you can't even add two numbers together in a generic context because of the fear of post-monomorphization errors should the addition overflow.

One suggestion has been to add a nopanic function attribute; the compiler would enforce that such functions don't panic and only call other nopanic functions. Then you could use nopanic functions in const expressions without adding bounds. One example of a nopanic function is wrapping_add, which could be used instead of normal addition.

...Except that makes no sense! In the unlikely event the addition overflows, it's almost certainly a bug – and the const evaluator knows it. Using wrapping_add would be asking the compiler not to tell you about the bug, just to avoid the spectre of a post-monomorphization error.

Surely, here, the cure is worse than the disease.

What are the alternatives?

One: don't use addition, or (lest someone propose arbitrary-precision integers as a solution to overflow) division, or array indexing, or unwrap(), or anything else that can fail. That's how you get a Turing tarpit.

Two: have const expressions come with compiler-checked proofs that, as long as the inputs are correct, they will never overflow any of their additions or panic any other way. Sounds cool. However it would require dependent types.

Three: add a "this expression is valid" bound, and just require any generic thing that wants to use const expressions to add those exact expressions to its list of bounds. That seems to be the current plan. It's not as obviously flawed as the other options. But it does mean that you'll often be in a context where you can't use some obvious const generic computation without sticking in semantically meaningless bounds, and as those bounds propagate up the stack, you'll end up with failures that are just as mysterious as "true" post-monomorphization errors.

I know I've gotten a bit off topic. After all, I'm talking about a problem that already exists in const generics, not one that's specific to types-as-values. And I suspect you may not have been proposing types-as-values in the first place.

But that's the thing. I think we're already headed for a reckoning on post-monomorphization errors, once const generics are fully implemented. I think we will end up deciding that post-monomorphization errors in some scenarios are worth the cost. And then the question of whether to hold the line against them here will be moot.

It will, as @PoignardAzur suggested, become less about absolute guarantees, more about ensuring that the 'path of least resistance' checks as many things as possible, as early as possible, and produces as high-quality diagnostics as possible.

Sidenote: Just because something is pre-monomorphization does not mean it has pretty diagnostics. One benefit of post-monomorphization errors via panics in const expressions would be allowing developers to generate their own error messages; I think those might often be easier to understand than the pre-monomorphization equivalents.

4 Likes