< 返回版块

Neutron3529 发表于 2024-08-12 18:07

Ord坑了……

按文档,BinaryHeap需要的是Ord,但事实上BinaryHeap需要的是derive(Ord)保证trait bound,然后用PartialOrd

我闭眼按文档把Ord改好之后,BinaryHeap表示看不见,

……

Update: 想到了一个终极解决方案

trait Sealed;
pub trait Derive: Sealed;
pub struct True {_cannot_being_constructed:()}
pub struct False {_cannot_being_constructed:()}
impl Sealed for True{}
impl Sealed for False{}
impl Derive for True{}
impl Derive for False{}
pub type DeriveDefault = 按edition决定是True还是False;
trait Ord {
    type Derive:Derive = DeriveDefault;
}
// 于是我们可以通过指定type Derive=Self来启动PartialOrd的impl
impl<T:Ord<Derive = True>> PartialOrd for T { ... }

留作练习:我们可以借此允许&str+&str -> String吗?

strcore里面,不允许分配内存,Stringalloc里面,于是根据孤儿规则不能实现impl<'a> Add<Output = String> for &'a str


于是我愤而去写ACP,书上中下三策

下策是只写PartialOrd,把Ordcmd变成provided method(于是OrdCopy/Eq之流长得也就差不多了,直接impl Ord for Foo{}解决一切)

中策是搞一个TotalOrdTotalEq,然后让TotalOrd自动实现OrdPartialOrdTotalEq抄一遍PartialEq但这玩意的语义是Eq语义

上策是……中策怎么看怎么觉得,PartialEq+Eq就应该叫Eq,而不是TotalEq,这个写成TotalOrd的东西,分明是PartialOrd+Ord……所以为什么不直接把Ord/Eq的实现改了呢?

以前是自底向上,先Partial之后实现自己,现在为什么不能自顶而下,从Eq/Ord直接实现PartialEq/PartialOrd呢 (甚至,为什么core不直接焊死一个impl<T:Copy> Clone for T呢)……

由此,写就上中下三策,上表于libs-team

……只希望自己不是在打单机……

(p.s.外部链接不能输入github,否则就会像这样,直接被firefox屏蔽:)


Ext Link: https://github.com/rust-lang/libs-team/issues/425

评论区

写评论
作者 Neutron3529 2024-08-14 12:55

这玩意还有个类似的derive_ord_xor_partial_ord

目前的Ord全都是问题……

一起去写ACP把这玩意扬了吧:)


👇
Foreverhighness: 让我想起了前段时间看到的一道 clippy lint non_canonical_partial_ord_impl

Foreverhighness 2024-08-13 15:48

让我想起了前段时间看到的一道 clippy lint non_canonical_partial_ord_impl

TinusgragLin 2024-08-13 09:43

因为标记需要手动完成

哦,就是说:如果你已经有了 T: PartialOrd,此时你还要 T: Ord 的话,就说明 partial_cmp 大概率就是 Some() 里面包个 cmp 的实现(要不然你不会想要 T: Ord),所以我们直接提供一个默认实现免去你再复制粘贴的烦恼?

第二种实现的问题很小,因为老代码并不会impl TotalOrd和TotalEq,新代码在使用TotalOrd和TotalEq

确实,我当时没看仔细,想得太激进了。关于这个方案的那一小节我总感觉是不是缺了 impl<T: Eq> TotalEq for T {}impl<T: Ord> TotalOrd for T {}?

Rust允许循环定义

这在逻辑上太奇怪了:对于已经实现了 trait A 的 T 实现 trait A...... what?? 我猜只有 T 的 trait 实现只在这一处定义的时候可以?

作者 Neutron3529 2024-08-13 09:03

我的第一种方案是把cmp的全部methods改成provided methods,于是我们可以直接

impl Ord for T {}

由此#[derive(Ord)]的实现自然也就变成了简单的impl Ord for $ty {}

在这种情况下,可以近似将Ord当成是和Copy/Eq一样的marker来使用。错误标记Ord的后果跟把String标记成Copy差不太多。这个并不是impl<T: Animal> Human for T {},因为标记需要手动完成。

第二种实现的问题很小,因为老代码并不会impl TotalOrd和TotalEq,新代码在使用TotalOrd和TotalEq之后会自动放弃 impl Eq/Ord (转而使用TotalOrd/TotalEq提供的默认实现),所以这玩意大概率不会有冲突。

至于

如果已经有 Ord: PartialOrd,为什么还要 impl<T: Ord> PartialOrd for T {} 呢:T: Ord 就说明已经有 T: PartialOrd 了呀。

Rust允许循环定义,因此你可以很轻松地实现如下代码:

struct T;
impl Ord for T {
    fn cmp(&self, other: &Self) -> Ordering {
        // Implement the actual logic
    }
}
impl PartialOrd for T {
    // 此时PartialOrd完全依赖于Ord的实现
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

至于改进derive……那是捎带的问题。

在我们统一了impl<T:X> PartialX for T {...}之后,derive是必须被解决掉的(目前一个很严重的问题是,Eq不是从PartialEq derive过去的,derive Eq会直接导致没有警告的逻辑问题。)

最好的解法当然是把逻辑冲突变成编译错误。


👇
TinusgragLin: 确实,刚才我试了一下,在 OrdPartialOrd 实现相左的情况下,>, <, >=, <= 这些运算似乎都采用了 PartialOrd 的实现,且如果 partial_cmp 返回 None 的话,就会出现 a >= ba <= b 全为假的情况。要我猜的话,这可能是为了在 IEEE754 浮点数只能实现 PartialOrd 的情况下,还能使用这些运算符比较方便地比较浮点数。

我觉得你的第一种方案 impl<T: PartialOrd> Ord for T {} 不可行,T: PartialOrd 从字面上就说了 T 的某些值之间有可能没有序关系(比如 IEEE754 中的 NaN 和其他值),那么请问 T 为什么可以实现所有值之间都存在序关系的 Ord trait 呢?这就好像 impl<T: Animal> Human for T {} 一样不符合逻辑;第二种方案似乎需要一个很大的 breaking change;第三种方案似乎有个逻辑问题,如果已经有 Ord: PartialOrd,为什么还要 impl<T: Ord> PartialOrd for T {} 呢:T: Ord 就说明已经有 T: PartialOrd 了呀。

PartialOrd, Ord, PartialEq, Eq 这四个确实在我刚开始入门 Rust 的时候也让我琢磨了一阵,我稍微 google 了一下,发现有关于优化这四个 trait derive 体验的提案:这个这个,但是似乎都没了下文。

TinusgragLin 2024-08-12 21:33

确实,刚才我试了一下,在 OrdPartialOrd 实现相左的情况下,>, <, >=, <= 这些运算似乎都采用了 PartialOrd 的实现,且如果 partial_cmp 返回 None 的话,就会出现 a >= ba <= b 全为假的情况。要我猜的话,这可能是为了在 IEEE754 浮点数只能实现 PartialOrd 的情况下,还能使用这些运算符比较方便地比较浮点数。

我觉得你的第一种方案 impl<T: PartialOrd> Ord for T {} 不可行,T: PartialOrd 从字面上就说了 T 的某些值之间有可能没有序关系(比如 IEEE754 中的 NaN 和其他值),那么请问 T 为什么可以实现所有值之间都存在序关系的 Ord trait 呢?这就好像 impl<T: Animal> Human for T {} 一样不符合逻辑;第二种方案似乎需要一个很大的 breaking change;第三种方案似乎有个逻辑问题,如果已经有 Ord: PartialOrd,为什么还要 impl<T: Ord> PartialOrd for T {} 呢:T: Ord 就说明已经有 T: PartialOrd 了呀。

PartialOrd, Ord, PartialEq, Eq 这四个确实在我刚开始入门 Rust 的时候也让我琢磨了一阵,我稍微 google 了一下,发现有关于优化这四个 trait derive 体验的提案:这个这个,但是似乎都没了下文。

1 共 5 条评论, 1 页