-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better error message for polymorphic recursion #4287
Comments
After adding
I was not able to fix this by adding |
In fact, I know of one other language that fails this test (well... Basic surely doesn't fail it, because this test is meaningless for Basic) — it's C++. |
As far as I know, Rust doesn't support polymorphic recursion and there aren't any plans to change that. The error message should probably be more informative, though (this isn't really about inlining, that's an implementation detail). It shouldn't be too hard to check for this in typeck. (I changed the issue title to reflect what the issue is.) |
That's a shame. I was really hoping that Rust is going to be something like "low-level Haskell". Seems like generics are really just C++ templates (without certain advantages). |
Is there a particular application you had in mind where you would need polymorphic recursion? Generics are unlike C++ templates because they're typechecked before expansion; this makes a huge difference in usability since type error messages refer to the code you wrote, not the code the compiler expanded. However, it's true that generics are less powerful than Haskell- or ML-style polymorphic, because Rust has no polymorphic types; items rather than types have type parameters. This was a design decision made very early on and so far there's been no particular reason we've seen to change it. |
@migmit In many ways, Rust is like low-level Haskell. That's why this test doesn't work! Because we are operating at a low level, we don't have uniform representation and implicit boxing and so forth. Therefore, like C++, we monomorphize generic functions---this implies that we would require an infinite set of functions to deal with recursive functions like the one you wrote here (note that other cases of polymorphic recursion work just fine, so long as they don't generate an infinite stream of types). I am going to close this as "not a bug, working as designed". |
@nikomatsakis I agree that it's working as designed, but I still think we should have a better error message than "overly deep expansion of inlined function", which doesn't say what the problem is. |
Someone came in IRC recently with this exact same problem (but I think they were just testing the limits of the language rather than trying to do anything serious) |
The usecase was encoding a complete binary tree as a type. |
I think it's worth clarifying here that in the two closed bugs immediately above, Rust isn't triggering an error at all, so polymorphic recursion sometimes goes completely unnoticed and just causes compilation to diverge. It doesn't always even trigger an inlining error. This makes it very hard to track down, so any improvement in detection and reporting would be greatly appreciated. Thanks, guys! |
assigning P-low. not 1.0 blocker. |
Modern form of the example code give us new output now (play). enum Nil {Nil}
struct Cons<T> {head: i32, tail: T}
trait Dot {fn dot(self, other: Self) -> i32;}
impl Dot for Nil {
fn dot(self, _:Nil) -> i32 {0}
}
impl<T:Dot> Dot for Cons<T> {
fn dot(self, other: Cons<T>) -> i32 {
self.head * other.head + self.tail.dot(other.tail)
}
}
fn test<T:Dot> (n: i32, i: i32, first: T, second: T) ->i32 {
match n {
0 => {first.dot(second)}
_ => {test (n-1, i+1, Cons {head:2*i+1, tail:first}, Cons{head:i*i, tail:second})}
}
}
fn main() {
let n = test(5, 0, Nil::Nil, Nil::Nil);
println!("{}", n)
} https://gist.github.com/pnkfelix/db9c9fc7a971289c17c7 pnkfelix: I took the liberty of moving the original content into a gist (linked above) because it was 200 lines of very regular output. |
Should this be closed now? |
I believe the error message here is still waiting to be improved. |
New (and sole) message in nightly:
Not pretty, but much better than before. Fixed? |
I don't consider this fixed, because I don't think the error message is good enough in most cases. Ideally we would detect polymorphic recursion explicitly (rather than having it caught by our other safe-guards against infinite-regression), and explain the origin of it in the source program. E.g. say something like (off the top of my head):
In this case it is a cycle of function calls that exposes polymorphic recursion, but in other cases like #33545, the polymorphic recursion arises more directly in a type like: enum E1<T> {
V1(T),
V2(Box<E1<E2<T>>>)
} where the type (so the error message would need to be adjusted accordingly for such cases.) In case its not clear, I still regard this as a low priority work item, but something that ideally would be addressed eventually. |
An even better approach (from the user's perspective) would be to allow polymorphic recursion so long as the sizes and operations matched, such that the types could be erased – i.e. polymorphic recursion is accepted as long as it could be compiled. But I think that would be very much not worth it. |
@birkenfeld not fixed because it doesn't always detect the recursion. The complete binary tree still hangs (with no output) during typeck, if you instantiate it: enum Perfect<T> { Tip(T), Fork(Box<Perfect<(T, T)>>) }
fn main() {
let _ = Perfect::Tip(42);
} |
I just ran into the hanging version of this in my own code. took a long time to figure out how to change it enough so that I would actually get the overflow error |
Triage: no change |
The best option I can think of is to report an error if type checking takes too long. |
(Rust typechecking being undecidable is not really a bug -- with a trait system as expressive as ours, this is to be expected.) |
Yes, hanging is a bug -- something there clearly needs a counter or so, similar to I was just commenting on the undecidable aspect. |
I updated the code to be modern Rust and I formatted it (playground): enum Nil {
Nil,
}
struct Cons<T> {
head: i32,
tail: T,
}
trait Dot: Sized {
fn dot(self, other: Self) -> i32;
}
impl Dot for Nil {
fn dot(self, _: Nil) -> i32 {
0
}
}
impl<T: Dot> Dot for Cons<T> {
fn dot(self, other: Cons<T>) -> i32 {
self.head * other.head + self.tail.dot(other.tail)
}
}
fn test<T: Dot>(n: i32, i: i32, first: T, second: T) -> i32 {
match n {
0 => first.dot(second),
_ => test(
n - 1,
i + 1,
Cons {
head: 2 * i + 1,
tail: first,
},
Cons {
head: i * i,
tail: second,
},
),
}
}
fn main() {
let n = test(5, 0, Nil::Nil, Nil::Nil);
println!("{}", n);
} Here's the current rustc output:
|
I'm going to close this since it now gives a type-length-limit error. |
It might still be a good idea to add a |
Can't compile:
Same code in Java, C# or Haskell works.
The text was updated successfully, but these errors were encountered: