Change:
From the : The comparison must satisfy, for all , and : (emphasis mine) focus on the final paragraph in the quote above and look at the following example () So either the library documentation is off or the implementations are flawed. Note that, technically, it’s impossible to enforce transitive existence of impls for the trait unless using operands of fully “external” types is completely prohibited: If I’m writing a crate providing a type and an as well as an , it seems like I’m following the rules set by ’s documentation. If I’m writing a crate providing a type and an as well as an , it seems like I’m following the rules set by ’s documentation. Now, if I’m writing a third crate that imports both and , then I’ll have as well as , but obviously is missing. In this example, the crates and each provided an where one of the operands, , was a type that’s fully external to the crate itself (in the sense that neither the type nor any of its generic arguments are part of , or part of a different crate that has the same owners as [ has no generic arguments to begin with]). label C-bug, T-libs
Those requirements are for values that are actually comparable.
Let me repeat what I already stated: and that final paragraph is
Ah right - we already changed that language for PartialEq: . We should make the same change here IMO.
The solution that takes isn’t perfect either. The following example is a bit artificial because I couldn’t find or come up with a real/realistic one (yet). According to those docs, if I have one crate with and a crate depending on then – AFAICT – this kind of implementation would be allowed However, now the crate could make a minor version change adding a implementation itself: AFAIK, minor version updates are allowed to add trait impls such as the one above. But now we have but
In what context are those implementations actually reasonable?
This documentation also doesn’t disallow something like since it doesn’t disallow a situation where <hrSimilarly, this wouldn’t be disallowed either where we’ll have <hrThese kinds of examples can also be used for new examples like in my previous comment demonstrating that semver makes it unclear which crate needs to ensure which kinds of properties: imagine being in a different crate (together with the two s involving ). Then a future version of the crate containing and could add the missing symmetric implementation and this change introduces a case of .
The documentation states useful properties that code working with partial equality can assume. I don't think it is necessary or even particularly useful to guard against every hypothetically possible malicious or bizarre combination of impls.
In any case, I agree that the conditions as in are better than before and better than the ones in .
That sounds reasonable. So the point is that if would type-check then it would be required to hold (and then would be forbidden), but there is a "gap" in the chain and thus this fails to be disallowed? This still requires to have a clearly weird notion of equality though, doesn't it? Namely it depends on the crate defining and and provides a way to (transitively) compare s and s in a way that disagrees with directly comparing them. So IMO it is quite clear where the bug is here, even if we don't go through the effort of making the docs completely water-tight in this regard. (We'd have to also state some symmetric cases of transitivity to achieve that.)
Yes That’s true… I agree, intuitively it’s clear My problem is perhaps more the problem that I’m not even sure if there exists a way to make it watertight without contradictions at all. And even if there is a way there might be multiple ways to make this mathematically accurate and watertight. And then one crate creator could intuitively have one understanding of the rules while another one could have a different, incompatible intuition (either intuition might be justified by one of the multiple ways to make the situation formally rigid), with the incompatibility of the approaches resulting in violation of even the minimal set of rules we’d like to have. TL;DR: What I already said, “semver makes it unclear which crate needs to ensure which kinds of properties”. We have no story of who’s responsibility for what in order to enforce the rules we want to have. The problem seems highly non-trivial because you’d need to consider semver as well as orphan rules. Maybe there’d be a need to establish additional orphan-rule-like restrictions, e.g. the example of a type being introduced as a means to – indirectly – compare two previously uncomparable types and . So this might be disallowed by some “orphan rule”-style convention of not introducing any way to compare two external types and even indirectly. Introducing direct comparison in this case is already prevented by ordinary orphan rules preventing , but the indirect case would IMO be best documented in the docs of the respective traits like .
Yeah I agree it's not entirely trivial. Basically, instead of reasoning about the impls that currently exist, you have to reason about the impls that might exist in the future -- "For all legal supersets of the current set of implemented traits, if and and , then ..." This can be made precise with a "multiple worlds" kinds of model -- coherence and I recall blog posts along those lines but can't find them right now. The statement in the doc must be interpreted not in the current worlds, but in any compatible (future) world.
This was briefly discussed in the library api meeting last week. Our conclusion was that since we already accepted for , we should accept the same change for .
The problem with how conditions have been changed is that now it contradicts the statement that implements . See this . Example code from the thread: This satisfies the conditions, but is inconsistent with it being a partial equivalence relation. Therefore any generic code bound by cannot assume that the relation between different types forms a partial equivalence relation, rendering these conditions more or less useless. I think the solution is to only require these conditions for and and not between different types. An even more drastic potential solution would be to only have these symmetry and transitivity requirements for and , and not for and . I haven't seen a practical use case requiring a partial equivalence relation or a partial order but not or .
The notion of a PER is defined on binary relations over some set X. I am not aware of it even being a well-defined question to ask whether a relation on (like your example) is a PER. Where are you taking that notion from? Your Wikipedia link does not provide a definition that could be applied here. We should probably clarify the docs to say that "if , then these rules imply that the relation is a PER".
Agreed! But what is the value of having such rules for if they don't even imply anything like PER? They still seem tricky to satisfy when different people implement different parts of this in different crates, even the weaker version. The rules seem totally useless if you can't even deduce basic equivalence properties between objects, such as "when a = x, a = y, b = x, then b = y". Agreed. That's one reason why I suggested weakening the conditions to only apply to . I am taking the notion that it should be a PER directly from the first line of documentation of , and from the name of the trait itself. You could in theory have PER between different types: it's just same thing as Wikipedia says, as a relation on . So my point was: my example violates PER for all possible ways that could be implemented, and violates various corollaries of these elements being in some partial equivalence relation. In another world, one would more reasonably define three traits like this: (no requirements, except perhaps that is the same as ) (partial equivalence relations) (equivalence relations) Frankly, I don't really see the practical value of even having a trait for "partial equivalence relations". Is there some code that specifically wants partial equivalence relations? I doubt it. Hence my other possible proposal: given that is defined on two types, treat it as if it was and not have any conditions on it. Having a trait require certain things for only might be a bit strange for generic implementation that doesn't know whether . But perhaps it's fine. I suspect the only reason the trait was conceived with that name in the first place is that somebody noticed that IEEE-754 floating point equality happens to satisfy this strange concept of partial equivalence relations, not because anything required such a trait specifically.
Your previous post presupposes that there is some definition of "PER on ", which is not the case. The stdlib docs also presuppose this, but your example doesn't demonstrate a problem with the stdlib docs, it just repeats the same mistake that the stdlib docs made by using a term outside of the domain it is defined on. That doesn't seem like a sensible definition to me. Symmetry is impossible to satisfy for elements in the symmetric difference of and , meaning those elements would be forced to be unrelated. (Rust enum types are nominal, so mathematically and are distinct values and the only heterogeneous PERs would be the empty relations.) When precedence for such a concept exists in mathematics, I think it would be foolish to deviate from that without a good justification. It's also not at all a strange concept; partial orders are pretty common and partial equivalence relations are the natural name for a partial order that is also symmetric. But anyway, we seem to agree that the docs should emphasize PERs less. It still seems like a useful fact to mention for the common case where .
My point is, my implementation of on is not a subset of any partial equivalence relation on , which is what one might expect. The practical issue with that is that there are certain properties you'd expect from a partial equivalence relation, even if you don't know how some of the comparisons work, and those properties are violated here. What? That makes no sense. First, by I meant the union of and (I guess the symmetric difference is the same thing as the union, since these are disjoint). But secondly: it's certainly possible to satisfy symmetry on the union of two types. For instance, the implementation of for and is symmetric and transitive for all combinations of values and types, and is indeed a partial equivalence relation on the union . I am not proposing deviating from anything... Just not using it. C++ doesn't use such a concept and is doing fine. But anyway, if you want to keep the case I think that is reasonable too.
The symmetric difference is . It's basically XOR on sets. If the rules are carefully picked to ensure that they form a PER for the homogeneous case, it would IMO be silly not to mention that.
Yes, and when and are disjoint, that is the same thing as , since and . Well yes, assuming the rules are kept then it should be mentioned that it's PER. I was talking in the context of a "plan B" I had proposed, where we remove those rules altogether:
My statement is that no element of can be on the LHS of a such-defined PER that is defined as a Rust instance of . Doing so would require having the same element also to the right of some pair in the relation, which is ill-typed in Rust. But anyway, your ad-hoc extension of PERs to heterogeneous types is not really relevant for us here.
It's not "my ad-hoc extension" -- before it was actually required by the docs.
No it was not. The docs never said to consider a relation on .
I feel like you're just trying to trap me on some word games. They didn't say so in those words, but what they did say mathematically implied it. The docs implied that if on T x U is defined, then it must also be defined on U x T, and also (by transitivity) on T x T and on U x U. In other words, the rules implied that it must be defined on , because mathematically . The requirements also stated that must be symmetric and transitive for all combinations of these types. Which is the definition of the concept "partial equivalence relation" on . The docs also did say explicitly that this constitutes a "partial equivalence relation". And they were correct. After the change, it's no longer correct except for T = U.
No they did not. They used (and use) the term PER in an ill-defined way. That doesn't imply anything, except that the docs need to be fixed.
intends to fix this for PartialEq. If that lands, we can then do the same for PartialOrd.

Discussion:
/// The equality relation `==` must satisfy the following conditions /// (for all `a`, `b`, `c` of type `A`, `B`, `C`): /// <del> /// - **Symmetric**: if `A: PartialEq<B>` and `B: PartialEq<A>`, then **`a == b` </del> <ins> /// - **Symmetry**: if `A: PartialEq<B>` and `B: PartialEq<A>`, then **`a == b` </ins> ///   implies `b == a`**; and /// <del> /// - **Transitive**: if `A: PartialEq<B>` and `B: PartialEq<C>` and `A: </del> <ins> /// - **Transitivity**: if `A: PartialEq<B>` and `B: PartialEq<C>` and `A: </ins> ///   PartialEq<C>`, then **`a == b` and `b == c` implies `a == c`**. <ins> ///   This must also work for longer chains, such as when `A: PartialEq<B>`, `B: PartialEq<C>`, ///   `C: PartialEq<D>`, and `A: PartialEq<D>` all exist. </ins> /// /// Note that the `B: PartialEq<A>` (symmetric) and `A: PartialEq<C>` /// (transitive) impls are not forced to exist, but these requirements apply

/// undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these /// methods. /// <ins> /// ## Cross-crate considerations /// /// Upholding the requirements stated above can become tricky when one crate implements `PartialEq` /// for a type of another crate (i.e., to allow comparing one of its own types with a type from the /// standard library). The recommendation is to never implement this trait for a foreign type. In /// other words, such a crate should do `impl PartialEq<ForeignType> for LocalType`, but it should /// *not* do `impl PartialEq<LocalType> for ForeignType`. /// /// This avoids the problem of transitive chains that criss-cross crate boundaries: for all local /// types `T`, you may assume that no other crate will add `impl`s that allow comparing `T == U`. In /// other words, if other crates add `impl`s that allow building longer transitive chains `U1 == ... /// == T == V1 == ...`, then all the types that appear to the right of `T` must be types that the /// crate defining `T` already knows about. This rules out transitive chains where downstream crates /// can add new `impl`s that "stitch together" comparisons of foreign types in ways that violate /// transitivity. /// /// Not having such foreign `impl`s also avoids forward compatibility issues where one crate adding /// more `PartialEq` implementations can cause build failures in downstream crates. /// </ins> /// ## Derivable /// /// This trait can be used with `#[derive]`. When `derive`d on structs, two

/// easy to accidentally make them disagree by deriving some of the traits and manually /// implementing others. /// <del> /// The comparison must satisfy, for all `a`, `b` and `c`: </del> <ins> /// The comparison relations must satisfy the following conditions /// (for all `a`, `b`, `c` of type `A`, `B`, `C`): </ins> /// <del> /// - transitivity: `a < b` and `b < c` implies `a < c`. The same must hold for both `==` and `>`. /// - duality: `a < b` if and only if `b > a`. </del> <ins> /// - **Transitivity**: if `A: PartialOrd<B>` and `B: PartialOrd<C>` and `A: ///   PartialOrd<C>`, then `a < b` and `b < c` implies `a < c`. The same must hold for both `==` and `>`. ///   This must also work for longer chains, such as when `A: PartialOrd<B>`, `B: PartialOrd<C>`, ///   `C: PartialOrd<D>`, and `A: PartialOrd<D>` all exist. /// - **Duality**: if `A: PartialOrd<B>` and `B: PartialOrd<A>`, then `a < b` if and only if `b > a`. </ins> /// <del> /// Note that these requirements mean that the trait itself must be implemented symmetrically and /// transitively: if `T: PartialOrd<U>` and `U: PartialOrd<V>` then `U: PartialOrd<T>` and `T: /// PartialOrd<V>`. </del> <ins> /// Note that the `B: PartialOrd<A>` (dual) and `A: PartialOrd<C>` /// (transitive) impls are not forced to exist, but these requirements apply /// whenever they do exist. </ins> /// /// Violating these requirements is a logic error. The behavior resulting from a logic error is not /// specified, but users of the trait must ensure that such logic errors do *not* result in /// undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these /// methods. /// <ins> /// ## Cross-crate considerations /// /// Upholding the requirements stated above can become tricky when one crate implements `PartialOrd` /// for a type of another crate (i.e., to allow comparing one of its own types with a type from the /// standard library). The recommendation is to never implement this trait for a foreign type. In /// other words, such a crate should do `impl PartialOrd<ForeignType> for LocalType`, but it should /// *not* do `impl PartialOrd<LocalType> for ForeignType`. /// /// This avoids the problem of transitive chains that criss-cross crate boundaries: for all local /// types `T`, you may assume that no other crate will add `impl`s that allow comparing `T < U`. In /// other words, if other crates add `impl`s that allow building longer transitive chains `U1 < ... /// < T < V1 < ...`, then all the types that appear to the right of `T` must be types that the crate /// defining `T` already knows about. This rules out transitive chains where downstream crates can /// add new `impl`s that "stitch together" comparisons of foreign types in ways that violate /// transitivity. /// /// Not having such foreign `impl`s also avoids forward compatibility issues where one crate adding /// more `PartialOrd` implementations can cause build failures in downstream crates. /// </ins> /// ## Corollaries /// /// The following corollaries follow from the above requirements: