foo::<()>()?; // or () = foo()?;
Even though it’s not a complicated change, the Rust maintainers were not willing to break 3,300 crates. Waffle was asked to work with the maintainers of common libraries to backport simple changes like the above (making a new patch version, which many Rust build environments will pick up automatically), in order to reduce the number of libraries depending on broken dependencies. Several library authors were willing to make the backports, but some refused on the grounds that those old versions were past their end of life. Those maintainers pointed out that users could stay on an older version of Rust or update to the maintained version of the library. Even so, the successful backports addressed 1,553 of the failing crates.
After fixing a handful of related problems to reduce the number of broken crates even further, the Rust maintainers eventually agreed that even though there would still be some broken code it was worth making the change to simplify the language. So, starting in Rust 1.99, the never type will be stable and Infallible will be a type alias for the never type. Users who find that this breaks their code have a few options:
On the one hand, this is a breaking change, and people may see code that had remained stable and working suddenly fail to compile. That could be seen as a violation of Rust’s commitment to backward compatibility. On the other hand, the problem is relatively rare, there are multiple simple ways to fix it, it has been warned about for years, and it has always been part of the plan for the language. Additionally, the Rust maintainers worked directly with the community to find and address the breakage, even going so far as to help backport fixes to long-dead versions of popular libraries. So, the whole process could also be seen as an affirmation of Rust’s commitment to backward compatibility.
In the future, people learning the language will hopefully find the never type just a little less special. Either way, most users of Rust will probably not be affected at all, but never say “never”.
One more thing to do
Posted Sep 8, 2026 16:57 UTC (Tue) by mss (subscriber, #138799) [Link] (5 responses)
One more thing to do
Posted Sep 9, 2026 8:19 UTC (Wed) by cyperpunks (subscriber, #39406) [Link]
One more thing to do
Posted Sep 9, 2026 11:48 UTC (Wed) by ralfj (subscriber, #172874) [Link] (3 responses)
One more thing to do
Posted Sep 9, 2026 11:54 UTC (Wed) by mss (subscriber, #138799) [Link] (2 responses)
One more thing to do
Posted Sep 9, 2026 12:55 UTC (Wed) by ralfj (subscriber, #172874) [Link] (1 responses)
One more thing to do
Posted Sep 9, 2026 13:10 UTC (Wed) by patrick_g (subscriber, #44470) [Link]
Why this code is actually a regression.
Posted Sep 8, 2026 17:04 UTC (Tue) by matthias (subscriber, #94967) [Link]
// No type specified. foo()?; I really wondered, why this code was a regression. And without the question mark, it would have always been an error, as the compiler would not be able to infer a type. However the question mark is syntactic sugar for
match foo() { Ok(v) => v Err(e) => return e.into() } Here, the compiler can infer a type. The error branch has the type ! which is changed to Infallible. As both branches need to have the same type, the compiler tries to find a type that is compatible with both branches. In most code, this would simply be the type of the Ok branch. But in this special case, the Ok branch has unspecified generic type T. And this is where the fallback type comes into play. In rust editions prior to 2024, this was the unit type (), which implements Default. So there is no problem. Since Edition 2024 and with rust 1.99 also for all older editions, this fallback will become !, which does not implement Default and we get a compile error.
This seems to be the only possibility of triggering a regression. We need to have two branches, where one is divergent (has type !) and the other has an unspecified generic type that cannot be inferred by other means and where it makes a difference whether we infer () or !. Seems to be quite a corner case.
It’s not a violation of promise!
Posted Sep 8, 2026 19:15 UTC (Tue) by khim (subscriber, #9252) [Link]
Infallible is not as bad as it sounds
Posted Sep 9, 2026 7:58 UTC (Wed) by NYKevin (subscriber, #129325) [Link]
Infallible is (was) defined as an empty enum, and Rust knows very well that empty enums cannot be instantiated. For example, you can legally write something like the following:
let Ok(s) = String::FromStr(“hello world!”);
Note the complete lack of an Err case. This works even without the never type stabilization, because Rust knows the Err() case can’t really exist. If this is known early enough for the above code to type-check successfully, then it obviously should also be known at trans (which is much later in the compilation process).
The real problem with Infallible is mostly that it has a silly name and does not auto-coerce at all. The latter can be worked around by writing match(x) {} where x is (or contains) an Infallible. The match statement does not require arms because (again) Rust knows that there are no possible values to match against, and does not require any match arms. This expression will coerce into an arbitrary type. But that’s more unwieldy than using !, which auto-coerces and doesn’t require this workaround.
Void is surprisingly useful.
Posted Sep 9, 2026 9:56 UTC (Wed) by dcoutts (subscriber, #5387) [Link] (22 responses)
Haskell has had a Void type in the standard library for over 10 years and it is surprisingly useful.
I guess Haskell borrowed the name void from C/C++.
In Haskell we represent it as a data type with no constructors, which is similar to Rust’s empty enumeration.
`data Void
– | Since ‘Void’ values logically don’t exist, this witnesses the – logical reasoning tool of "ex falso quodlibet".
absurd :: Void -> a
absurd a = case a of {}
Note the cute empty pattern matchof {}`, showing to the compiler that there are no cases. This empty pattern match trick is also useful in other contexts to show that certain cases are impossible (and thus don’t need to be handled).
There isn’t special compiler support for Void and it is not inserted as a last resort. In Haskell, functions that (very obviously) loop can be inferred to have a polymorphic result type, for example:
foo n = foo (n+1) This will be inferred to have type
foo :: forall a b. Num a => a -> b There’s no special case in the language or type system here, this is just the most general type and thus the inferred type.
Notice the result is a universally quantified type: for any b. This can be instantiated at a call site at any type. The article describes this as coercion, but in a type system with polymorphism it’s better understood as instantiating a polymorphic type variable for another (possibly concrete) type.
Void is surprisingly useful.
Posted Sep 9, 2026 11:47 UTC (Wed) by ralfj (subscriber, #172874) [Link] (21 responses)
Note that Haskell’s Void and C’s void are very different types. If we map them both to Rust, Haskell’s Void is like ! (the empty type) and C’s void is like () (the unit type). The fact that C’s void is often described as a “type with no values” is extremely misleading and confusing: C’s void has exactly one value, and because there is only one value you don’t have to spell it out and it encodes 0 bits of information. In Rust if a function has return type () you also don’t need to spell out its return value, but you can if you want – the syntax for the only value of the unit type is () as well.
“void” sounds like it should be an empty type, so IMO it is very unfortunate that C picked this term.
Void is surprisingly useful.
Posted Sep 9, 2026 12:50 UTC (Wed) by tux3 (subscriber, #101245) [Link] (13 responses)
In C++ this forces templates to special-case void. There was a proposal to make void more regular, but that wasn’t going to happen without breakage, so C++ added std::monostate instead, which is actually intended as a unit type.
Void is surprisingly useful.
Posted Sep 9, 2026 12:54 UTC (Wed) by ralfj (subscriber, #172874) [Link] (12 responses)
Void is surprisingly useful.
Posted Sep 9, 2026 13:42 UTC (Wed) by iabervon (subscriber, #722) [Link] (11 responses)
Void is surprisingly useful.
Posted Sep 9, 2026 14:52 UTC (Wed) by alx.manpages (subscriber, #145117) [Link] (1 responses)
“void (*)(int)” is “function that takes int and calling it is a statement rather than an expression”
void functions still produce expressions. This can be seen in the comma operator:
`void f(void);
42, f(), 42;
exit() returns void, but it's still usable as an expression. You just can't assign it to something, because variables of typevoid` are not allowed. But that doesn’t make them statements.
Void is surprisingly useful.
Posted Sep 9, 2026 16:17 UTC (Wed) by NYKevin (subscriber, #129325) [Link]
(I was going to say “void can’t be an rvalue,” but your example is indeed an rvalue!)
Void is surprisingly useful.
Posted Sep 9, 2026 14:55 UTC (Wed) by alx.manpages (subscriber, #145117) [Link] (8 responses)
I’m not sure I understand this. Would you mind rephrasing? Or maybe (or also) showing examples?
and you can make your parameterized types uniform by including some types that the compiler optimizes (like having “function that returns a value you must discard” instead), but that’s not how C was designed, so void ends up with all the weirdness that you get if you try to have a value that embodies the effects of not using something optional.
Void is surprisingly useful.
Posted Sep 9, 2026 16:40 UTC (Wed) by NYKevin (subscriber, #129325) [Link] (7 responses)
A user-supplied callback function might be void, so we need not propagate its return value.
The user might only want to use part of our container (e.g. using a hash table as a hash set), so we need not store the rest.
A user-supplied function might be infallible, so we need not handle errors.
In other words, the vast majority of cases just look like deleting some code that we don’t need. So you don’t really need optional type parameters, you just need types that tell the compiler to delete some code. That gives us () and !:
() (“unit”) means roughly “don’t emit code to store or pass along values of this type.” It’s a type whose only value is always constant-folded out of existence. It’s good for cases where we want the code to do basically the same thing, except without storing a value.
! (“never”) means roughly “don’t emit code in any scope where a value of this type is observed to exist.” It’s a type with no values, so you don’t need any code to handle it. It’s good for cases where we want some branch(es) to disappear entirely, e.g. for removing error handling or otherwise forcing compile-time evaluation of something.
! has the additional behavior of making some pattern matches infallible. If we have a Result<V, !>, it may be infallibly pattern-matched against Ok(v) because Rust knows the Err() case can’t exist. This doesn’t work with Result<V, ()>, because that means “there is an error case, but no error information is provided” (similar to Option
Void is surprisingly useful.
Posted Sep 9, 2026 17:05 UTC (Wed) by iabervon (subscriber, #722) [Link]
Void is surprisingly useful.
Posted Sep 9, 2026 21:15 UTC (Wed) by alx.manpages (subscriber, #145117) [Link] (5 responses)
The problem with optional type parameters, in generic code (which is the only kind of code that has “type parameters” of any kind, optional or not), is combinatorial blowup. For each optional type parameter, we must write twice as many implementations.
Not necessarily. Here’s some generic code with a single implementation:
#define rvalue(lv) ((void)0, (lv)) #define typeof_typename(T) typeof(*(typeof(T) *){_Generic(0, T: NULL, default: NULL)}) #define mallocarray(...) reallocarray(NULL, __VA_ARGS__) #define malloc_T(n, T) rvalue((typeof_typename(T) *){mallocarray(n, sizeof(T))}) malloc_T() is implemented internally through void* (through reallocarray(3)), but exposes a type-generic API. We could say reallocarray(3) is unsafe, but the exposed API of malloc_T() is safe. And we didn’t need an exponential number of implementations. But it can certainly be used with hundreds of types.
int *p = malloc_T(42, int); long *q = malloc_T(6, long); struct foo *r = malloc_T(7, struct foo); Am I misunderstanding something?
Void is surprisingly useful.
Posted Sep 10, 2026 0:19 UTC (Thu) by NYKevin (subscriber, #129325) [Link] (4 responses)
Void is surprisingly useful.
Posted Sep 10, 2026 9:52 UTC (Thu) by alx.manpages (subscriber, #145117) [Link] (3 responses)
Indeed, this API lets you write malloc_T(42, void). It’s not allowed in the ISO C dialect, because sizeof(void) is not valid there. But in GNU C, sizeof(void)==1, and thus this is perfectly allowed.
`alx@devuan:~/tmp$ cat malloc.c #include <stdlib.h>
#define rvalue(lv) ((void)0, (lv)) #define typeof_typename(T) typeof(*(typeof(T) *){_Generic(0, T: NULL, default: NULL)}) #define mallocarray(…) reallocarray(NULL, VA_ARGS) #define malloc_T(n, T) rvalue((typeof_typename(T) *){mallocarray(n, sizeof(T))})
int main(void) { void *p = malloc_T(42, void);
/*
- The memory in ‘p’ doesn’t have an effective type yet at this
- point. This is useful for example for creating a memory pool
- from which to use memory later. */
int *i = p; *i = 7;
/*
- The first byte of the pool now has an effective type of
- ‘int’. The rest remains without effective type. */
free(p); } `
$ gcc -Wall -Wextra malloc.c
$
Void is surprisingly useful.
Posted Sep 10, 2026 9:53 UTC (Thu) by alx.manpages (subscriber, #145117) [Link]
` /*
- The first byte of the pool now has an effective type of
- ‘int’. The rest remains without effective type. */ `
Oh, well, I meant the first 4 bytes, of course. :)
Void is surprisingly useful.
Posted Sep 11, 2026 22:29 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (1 responses)
Of course, there has to be some allowance for special cases where you don’t need multiple implementations, because you could always write a function that does not actually use its type parameter. But my point is that in the general case, where you may want to write arbitrary code, you do face a combinatorial explosion. There might happen to be a few special cases where it’s not strictly necessary… but Rust in particular is not a fan of this sort of thing.
In Rust (unlike C++), generic definitions have to be valid for all possible choices of parameters, not just for the particular invocation(s) that happen to appear in your project. This is essential for backwards compatibility. If someone calls into your function in version 1.0 with a given set of generic parameters, then that same set of parameters ought to work in version 1.1 (assuming you follow semver or something like semver). Rust upholds that guarantee without forcing you to test all possible (or all “reasonable”) combinations of parameters manually, because it does type checking before the generics are monomorphized. By the time parameters are actually substituted, we already know that the resulting code will compile. And you know that you’re backwards compatible as long as you didn’t change the signature (which includes information about which types are “allowed” to be used as type parameters).
So now you can see the issue: If we want to allow for “optional” type parameters in Rust, we need to fit them into the type checking scheme. At that point, it is a lot easier to say “OK, we’re promoting these empty values to full types and giving them completely regular type-level semantics” than it is to say “OK, we’re going to litter the generic type logic with dozens of special cases scattered all over the place just in case a type parameter turns out to be empty.”
Void is surprisingly useful.
Posted Sep 11, 2026 23:25 UTC (Fri) by alx.manpages (subscriber, #145117) [Link]
I say it now. ISO C is like speaking standard Latin: nobody does it, and nobody ever did (except maybe a few Romans, during a few years, and maybe neither). It’s good as a guideline on which to base dialects, not as an actual dialect meant to be directly useful. Very few programs (none that are non-trivial?) are strictly conforming to ISO C (and even less to ISO C90).
But my point is that in the general case, where you may want to write arbitrary code, you do face a combinatorial explosion. There might happen to be a few special cases where it’s not strictly necessary…
I write a lot of type-generic code using GNU C23, and only have a few places where I’ve had to duplicate code; they are the special case, IME. We’re working to improve the language in ways that those special cases may not be needed in the future.
but Rust in particular is not a fan of this sort of thing.
I don’t write any Rust, but this seems consistent with what I’ve heard, and with the little Rust I’ve read: it tends to be more verbose than C.
Regular void
Posted Sep 10, 2026 9:57 UTC (Thu) by tialaramex (subscriber, #21167) [Link] (6 responses)