Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Adding impl IntoIterator for T can be breaking if impl IntoIterator for &T exists #518

Open
not-my-profile opened this issue Aug 16, 2023 · 1 comment

Comments

@not-my-profile
Copy link

not-my-profile commented Aug 16, 2023

Adding an impl IntoIterator for T can be a breaking change if an impl IntoIterator for &T already exists and the new implementation has a different associated type. Though this pitfall is not specific to IntoIterator and can be illustrated with a simpler example:

pub mod api {
    pub trait IntoX {
        type X;

        fn into_x(self) -> Self::X;
    }

    pub struct Bar;

    impl IntoX for &Bar {
        type X = bool;

        fn into_x(self) -> Self::X {
            true
        }
    }

    // impl IntoX for Bar {
    //     type X = i32;

    //     fn into_x(self) -> Self::X {
    //         3
    //     }
    // }
}

// API consumer code
use api::{Bar, IntoX};

fn test(b: Bar) {
    assert!(b.into_x());
}

Uncommenting the commented out impl will make the given consumer code fail to compile, since the code will then use the new impl which returns a different type.

I looked over the checks listed in #5 and couldn't find any bullet point for this specific issue ... but I may very well be missing it, there are a lot of bullet points 😅 ... I also didn't find a single other mention of IntoIterator in the issue tracker, so I'm just opening a new issue for this.

@obi1kenobi
Copy link
Owner

Nice catch that this is breaking!

Unfortunately, I believe this is one of those "breaking but not semver-major" changes that Rust allows. I believe the breakage here can be avoided by using a more explicit form like <Bar as IntoX>::into_x(b), which is one of the edge cases that is explicitly defined as breaking but not major.

That said, two things:

  • Semver-major or not, this feels like a footgun. Feels like it might be a good candidate for a clippy lint, like "same trait implemented on type and reference with unrelated associated types"?
  • In the future, I'd love to have a "this isn't semver-major but it might annoy your downstream users" category of lints, either inside cargo-semver-checks itself or as its own separate tool. If clippy decides this lint isn't a fit, it would be great to have there instead.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants