Skip to content
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

Infinite recursion while resolving traits #88925

Closed
stepchowfun opened this issue Sep 14, 2021 · 4 comments
Closed

Infinite recursion while resolving traits #88925

stepchowfun opened this issue Sep 14, 2021 · 4 comments
Labels
A-monomorphization Area: Monomorphization C-bug Category: This is a bug. I-monomorphization Issue: An error at monomorphization time. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@stepchowfun
Copy link

stepchowfun commented Sep 14, 2021

Hi all! I'm running into an issue that is difficult for me to describe other than by giving a simple example. I believe the following code should compile, but instead it seems like the compiler gets stuck in a loop:

use std::{
    fs::File,
    io::{Result, Write},
};

pub trait Serialize {
    fn serialize<T: Write>(&self, writer: T) -> Result<()>;
}

pub enum Tree {
    Leaf(bool),
    Parent(Vec<Tree>),
}

impl Serialize for Tree {
    fn serialize<T: Write>(&self, mut writer: T) -> Result<()> {
        match self {
            Tree::Leaf(_) => Ok(()),
            Tree::Parent(children) => {
                // let mut writer: Box<dyn Write> = Box::new(writer);

                for child in children {
                    child.serialize(writer.by_ref())?;
                }

                Ok(())
            }
        }
    }
}

fn main() -> Result<()> {
    let mut buffer = File::create("tree.txt")?;
    Tree::Parent(vec![Tree::Leaf(true)]).serialize(&mut buffer)
}

As is, the compiler gives this error when running cargo build:

$ cargo build
   Compiling example v0.1.0 (/Users/stephanboyer/Desktop/personal/projects/typical/integration-tests/rust)
error: reached the recursion limit while instantiating `<Tree as Serialize>::serialize::...t &mut &mut &mut &mut &mut File>`
  --> src/main.rs:24:21
   |
24 |                     child.serialize(writer.by_ref())?;
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: `<Tree as Serialize>::serialize` defined here
  --> src/main.rs:16:5
   |
16 |     fn serialize<T: Write>(&self, mut writer: T) -> Result<()> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: the full type name has been written to '/Users/stephanboyer/Desktop/personal/projects/typical/integration-tests/rust/target/debug/deps/example.long-type.txt'

error: could not compile `example` due to previous error

Since writer: T implements Write (as T has the trait bound), I would expect writer.by_ref() to also implement Write, per the documentation of by_ref(). But the compiler does not seem satisfied by that, and instead seems to get stuck in some kind of loop (until the recursion limit is reached).

The build succeeds if the commented line is uncommented. It seems boxing the writer into a trait object terminates the recursion. But that requires an extra allocation, so it's not a satisfying workaround.

(Strangely, cargo check succeeds on both versions and doesn't seem to run into this issue.)

Thanks for looking into this, and I apologize if this is a duplicate of another issue. I tried searching and found a few possible instances of people running into the compiler's recursion limit, but it was hard for me to tell if it was the same issue as this one.

Meta

rustc --version --verbose:

rustc 1.55.0 (c8dfcfe04 2021-09-06)
binary: rustc
commit-hash: c8dfcfe046a7680554bf4eb612bad840e7631c4b
commit-date: 2021-09-06
host: x86_64-apple-darwin
release: 1.55.0
LLVM version: 12.0.1
Backtrace

$ RUST_BACKTRACE=1 cargo build

   Compiling example v0.1.0 (/Users/stephanboyer/Desktop/personal/projects/typical/integration-tests/rust)
error: reached the recursion limit while instantiating `<Tree as Serialize>::serialize::...t &mut &mut &mut &mut &mut File>`
  --> src/main.rs:24:21
   |
24 |                     child.serialize(writer.by_ref())?;
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: `<Tree as Serialize>::serialize` defined here
  --> src/main.rs:16:5
   |
16 |     fn serialize<T: Write>(&self, mut writer: T) -> Result<()> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: the full type name has been written to '/Users/stephanboyer/Desktop/personal/projects/typical/integration-tests/rust/target/debug/deps/example.long-type.txt'

error: could not compile `example` due to previous error

@stepchowfun stepchowfun added the C-bug Category: This is a bug. label Sep 14, 2021
@Aaron1011
Copy link
Member

Aaron1011 commented Sep 15, 2021

Inside your implementation of serialize, you recursively call serialize instantiated with a new type - &mut T, where T is the type you started out with. This occurs because each level of recursion is adding a new &mut reference - you initially have a &mut File, and obtain a &mut &mut File by calling by_ref on the &mut T via its Write impl.

You could either introduce a trait object (a &mut dyn Write should work as well), or change your Serialize trait to take in a &mut T, which can be directly passed to the recursive serialize call.

@stepchowfun
Copy link
Author

I understand that my example features polymorphic recursion. But why is that forbidden? Perhaps this limitation is a consequence of monomorphization?

or change your Serialize trait to take in a &mut T, which can be directly passed to the recursive serialize call.

After a bit of exploration, I discovered that taking the writer by reference (writer: &mut T, as you suggested), I can reborrow it for the recursive calls (&mut *writer). (I originally avoided doing this because it seems to be unidiomatic to take writers by reference.) Strictly speaking, I cannot logically pass the mutable reference directly to the recursive calls in a loop, since mutable references are not Copy. But apparently the Rust compiler will implicitly do the reborrowing in some cases (including in this simple situation), giving the illusion that I can simply reuse the reference (writer). Convenient, if a little too magical for my taste.

So I can do that to work around this issue (thanks for suggesting it!), but I still find it counterintuitive that the code cannot be completely type checked before instantiating generics (which seems like a codegen concern, i.e., part of the compiler "back end").

@tavianator
Copy link
Contributor

Indeed, Rust doesn't support polymorphic recursion: #4287, exactly because of monomorphization.

@fmease fmease added A-monomorphization Area: Monomorphization T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. I-monomorphization Issue: An error at monomorphization time. and removed needs-triage-legacy labels Jan 25, 2024
@fmease
Copy link
Member

fmease commented Jan 25, 2024

Closing as wontfix then.

@fmease fmease closed this as not planned Won't fix, can't repro, duplicate, stale Jan 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-monomorphization Area: Monomorphization C-bug Category: This is a bug. I-monomorphization Issue: An error at monomorphization time. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants