Structural equality - confusing docs

The idea of StructuralEq and StructuralPartialEq is pretty clear - making sure the type compatible with constant pattern matching. As docs say that couldn't be achieved with bare Eq and PartialEq, because of cases like this:

In a more ideal world, we could check that requirement by just checking that the given type implements both the StructuralPartialEq trait and the Eq trait. However, you can have ADTs that do derive(PartialEq, Eq), and be a case that we want the compiler to accept, and yet the constant’s type fails to implement Eq.

Namely, a case like this:

#[derive(PartialEq, Eq)]
struct Wrap<X>(X);

fn higher_order(_: &()) { }

const CFN: Wrap<fn(&())> = Wrap(higher_order);

fn main() {
    match CFN {
        CFN => {}
        _ => {}
    }
}

(The problem in the above code is that Wrap<fn(&())> does not implement PartialEq, nor Eq, because for<'a> fn(&'a _) does not implement those traits.)

Therefore, we cannot rely on naive check for StructuralPartialEq and mere Eq.

As a hack to work around this, we use two separate traits injected by each of the two derives (#[derive(PartialEq)] and #[derive(Eq)]) and check that both of them are present as part of structural-match checking.

I recreated the example, like this:

use std::fmt::Debug;

#[derive(PartialEq, Eq, Debug)]
struct Wrap<X>(X);

fn higher_order(_: &()) { }

const CFN: Wrap<fn(&())> = Wrap(higher_order);

fn main() {
    check(CFN);
}

fn check<T: Eq + PartialEq + Debug>(something: T) {
    println!("{:?}", something);
}

...and it looks like it does implement Eq and PartialEq. Am I missing something important here?

Stale documentation related to coherence around implementations on higher-ranked and non-higher-ranked function pointers.[1] The documentation was accurate until Januaray 2019. Maybe add a comment to that issue that 4.5 years later, it's still confusing people. (Dragging on as the soundness or desirability of that feature keeps coming into question.)

trait Trait {}
impl<A> Trait for fn(A) {}
impl<A> Trait for fn(&A) {}
warning: conflicting implementations of trait `Trait` for type `fn(&_)`
 --> src/lib.rs:3:1
  |
2 | impl<A> Trait for fn(A) {}
  | ----------------------- first implementation here
3 | impl<A> Trait for fn(&A) {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `fn(&_)`
  |
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #56105 <https://github.com/rust-lang/rust/issues/56105>
  = note: this behavior recently changed as a result of a bug fix; see rust-lang/rust#56105 for details
  = note: `#[warn(coherence_leak_check)]` on by default

warning: `playground` (lib) generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.34s

Note that to the best of my knowledge, the warning is a bald-faced lie and the current plan is still to accept anything that still compiles but gets the warning.


  1. and other types more generally ↩︎

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.