Type resolution on complex type seems to diverge and then crash the compiler
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
I tried this code:
struct Succ<T> {
inner: std::marker::PhantomData<T>,
}
struct Zero;
type One = Succ<Zero>;
type Two = Succ<One>;
type Three = Succ<Two>;
trait Add<T> {
type Output;
}
impl<U> Add<Zero> for U {
type Output = U;
}
impl<T, U> Add<Succ<T>> for U
where
U: Add<T>,
{
type Output = Succ<<U as Add<T>>::Output>;
}
trait Mult<T> {
type Output;
}
impl Mult<Zero> for Zero {
type Output = Zero;
}
impl<U> Mult<Zero> for Succ<U> {
type Output = Zero;
}
impl<T, U> Mult<Succ<T>> for U
where
U: Mult<T> + Add<<U as Mult<T>>::Output>,
{
// (t+1)*u = u*t + u
type Output = <U as Add<<U as Mult<T>>::Output>>::Output;
}
trait Sub {
type Output;
}
impl<T> Sub for Succ<T> {
type Output = T;
}
trait Not {
type Output;
}
impl Not for Zero {
type Output = One;
}
impl Not for One {
type Output = Zero;
}
trait IsEven {
type Output;
}
impl IsEven for Zero {
type Output = One;
}
impl<T> IsEven for Succ<T>
where
T: IsEven,
<T as IsEven>::Output: Not,
{
// 1 - IsEven<T>
type Output = <<T as IsEven>::Output as Not>::Output;
}
trait IsOdd {
type Output;
}
impl<T> IsOdd for T
where
T: IsEven,
<T as IsEven>::Output: Not,
{
type Output = <<T as IsEven>::Output as Not>::Output;
}
trait Half {
type Output;
}
impl Half for Zero {
type Output = Zero;
}
impl Half for One {
type Output = Zero;
}
impl<T> Half for Succ<Succ<T>>
where
T: Half,
{
type Output = Succ<<T as Half>::Output>;
}
trait Syracuse {
type Output;
}
impl<T> Syracuse for T
where
T: Half,
T: IsEven,
<T as Half>::Output: Mult<<T as IsEven>::Output>,
T: Mult<Three>,
T: IsOdd,
Succ<<T as Mult<Three>>::Output>: Mult<<T as IsOdd>::Output>,
<Succ<<T as Mult<Succ<Succ<Succ<Zero>>>>>::Output> as Mult<<T as IsOdd>::Output>>::Output:
Add<<<T as Half>::Output as Mult<<T as IsEven>::Output>>::Output>,
{
// (3t+1)*isodd(t) + (t/2)*(1-isodd(t))
type Output =
<<Succ<<T as Mult<Three>>::Output> as Mult<<T as IsOdd>::Output>>::Output as Add<
<<T as Half>::Output as Mult<<T as IsEven>::Output>>::Output,
>>::Output;
}
fn main() {
println!("{}", std::any::type_name::<<Three as Syracuse>::Output>())
}
It tries to implement the Syracuse sequence using Peano integers and the Rust type system (I know...).
I expected it to compile after maybe adjusting the recursion limit. Instead, I either get a compiler error of the type:
error[E0275]: overflow evaluating the requirement `Succ<_>: Mult<Succ<_>>`
|
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`syracuse2`)
note: required for `Succ<_>` to implement `Mult<Succ<Succ<_>>>`
--> src/syracuse2.rs:41:12
|
41 | impl<T, U> Mult<Succ<T>> for U
| ^^^^^^^^^^^^^ ^
42 | where
43 | U: Mult<T> + Add<<U as Mult<T>>::Output>,
| --------------------------- unsatisfied trait bound introduced here
= note: 126 redundant requirements hidden
= note: required for `Succ<_>` to implement `Mult<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<Succ<_>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`
note: required for `Self` to implement `Syracuse`
--> src/syracuse2.rs:121:9
|
121 | impl<T> Syracuse for T
| ^^^^^^^^ ^
...
130 | Add<<<T as Half>::Output as Mult<<T as IsEven>::Output>>::Output>,
| ----------------------------------------------------------------- unsatisfied trait bound introduced here
For more information about this error, try `rustc --explain E0275`.
error: could not compile `syracuse` (bin "syracuse2") due to 1 previous error
Or, after increasing the recursion limit, after around 690, I get an ICE instead:
error: rustc interrupted by SIGSEGV, printing backtrace
/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/librustc_driver-766f7798aafe0dd0.so(+0x2e7b2b3)[0x7b2822c7b2b3]
/lib/x86_64-linux-gnu/libc.so.6(+0x42520)[0x7b281fa42520]
/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/librustc_driver-766f7798aafe0dd0.so(+0x422e6e7)[0x7b282402e6e7]
### cycle encountered after 3 frames with period 4
/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/librustc_driver-766f7798aafe0dd0.so(+0x422e9ef)[0x7b282402e9ef]
/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/librustc_driver-766f7798aafe0dd0.so(+0x422e9ef)[0x7b282402e9ef]
/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/librustc_driver-766f7798aafe0dd0.so(+0x422e9ef)[0x7b282402e9ef]
/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/librustc_driver-766f7798aafe0dd0.so(+0x422e9ef)[0x7b282402e9ef]
### recursed 63 times
/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/librustc_driver-766f7798aafe0dd0.so(+0x422e9ef)[0x7b282402e9ef]
note: rustc unexpectedly overflowed its stack! this is a bug
note: maximum backtrace depth reached, frames may have been lost
note: we would appreciate a report at https://github.com/rust-lang/rust
error: could not compile `syracuse` (bin "syracuse2")
Caused by:
process didn't exit successfully: `/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc --crate-name syracuse2 --edition=2021 src/syracuse2.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=129 --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 -C metadata=df84f5d7ccac5ffc -C extra-filename=-df84f5d7ccac5ffc --out-dir <REDACTED>/target/debug/deps -C incremental=<REDACTED>/target/debug/incremental -L dependency=<REDACTED>target/debug/deps` (signal: 11, SIGSEGV: invalid memory reference)
Strangely enough, when factoring out the two parts of the final addition, the compiler happily compiles my code:
struct Succ<T> {
inner: std::marker::PhantomData<T>,
}
struct Zero;
type One = Succ<Zero>;
type Two = Succ<One>;
type Three = Succ<Two>;
trait Add<T> {
type Output;
}
impl<U> Add<Zero> for U {
type Output = U;
}
impl<T, U> Add<Succ<T>> for U
where
U: Add<T>,
{
type Output = Succ<<U as Add<T>>::Output>;
}
trait Mult<T> {
type Output;
}
impl Mult<Zero> for Zero {
type Output = Zero;
}
impl<U> Mult<Zero> for Succ<U> {
type Output = Zero;
}
impl<T, U> Mult<Succ<T>> for U
where
U: Mult<T> + Add<<U as Mult<T>>::Output>,
{
// (t+1)*u = u*t + u
type Output = <U as Add<<U as Mult<T>>::Output>>::Output;
}
trait Sub {
type Output;
}
impl<T> Sub for Succ<T> {
type Output = T;
}
trait Not {
type Output;
}
impl Not for Zero {
type Output = One;
}
impl Not for One {
type Output = Zero;
}
trait IsEven {
type Output;
}
impl IsEven for Zero {
type Output = One;
}
impl<T> IsEven for Succ<T>
where
T: IsEven,
<T as IsEven>::Output: Not,
{
// 1 - IsEven<T>
type Output = <<T as IsEven>::Output as Not>::Output;
}
trait IsOdd {
type Output;
}
impl<T> IsOdd for T
where
T: IsEven,
<T as IsEven>::Output: Not,
{
type Output = <<T as IsEven>::Output as Not>::Output;
}
trait Half {
type Output;
}
impl Half for Zero {
type Output = Zero;
}
impl Half for One {
type Output = Zero;
}
impl<T> Half for Succ<Succ<T>>
where
T: Half,
{
type Output = Succ<<T as Half>::Output>;
}
trait SyracuseEven {
type Output;
}
impl<T> SyracuseEven for T
where
T: Half,
T: IsEven,
<T as Half>::Output: Mult<<T as IsEven>::Output>,
{
type Output = <<T as Half>::Output as Mult<<T as IsEven>::Output>>::Output;
}
trait SyracuseOdd {
type Output;
}
impl<T> SyracuseOdd for T
where
T: Mult<Three>,
T: IsOdd,
Succ<<T as Mult<Three>>::Output>: Mult<<T as IsOdd>::Output>,
{
type Output = <Succ<<T as Mult<Three>>::Output> as Mult<<T as IsOdd>::Output>>::Output;
}
trait Syracuse: SyracuseEven + SyracuseOdd {
type Output;
}
impl<T> Syracuse for T
where
T: SyracuseEven + SyracuseOdd,
<T as SyracuseEven>::Output: Add<<T as SyracuseOdd>::Output>,
{
type Output = <<T as SyracuseEven>::Output as Add<<T as SyracuseOdd>::Output>>::Output;
}
fn main() {
println!("{}", std::any::type_name::<<Three as Syracuse>::Output>()) // This compiles and gives the expected output, i.e. 10
}
I still get the same ICE when trying to compile with larger numbers and adjusting the recursion limit, though.
Meta
rustc --version --verbose:
Tested using
rustc 1.76.0 (07dca489a 2024-02-04)
binary: rustc
commit-hash: 07dca489ac2d933c78d3c5158e3f43beefeb02ce
commit-date: 2024-02-04
host: x86_64-unknown-linux-gnu
release: 1.76.0
LLVM version: 17.0.6
as well as
rustc 1.78.0-nightly (9c3ad802d 2024-03-07)
binary: rustc
commit-hash: 9c3ad802d9b9633d60d3a74668eb1be819212d34
commit-date: 2024-03-07
host: x86_64-unknown-linux-gnu
release: 1.78.0-nightly
LLVM version: 18.1.0
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the issue with the provided program in src/syracuse2.rs using the reported stable and nightly rustc versions, first at the default recursion limit and then with the increased limit. Compare the overflow diagnostic with the SIGSEGV and stack-overflow behavior; done means the compiler no longer crashes on this input and produces a controlled result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100