【问题标题】:Making a generic From/Into shortcut for two-steps From为两步 From 制作通用 From/Into 快捷方式
【发布时间】:2021-04-30 05:05:00
【问题描述】:

StructA 实现 From<StructB>StructB 实现 From<S>

我一般如何实现“快捷方式”Into<StructA> for SFrom<S> for StructA

如果这是一个坏主意,请告诉我。但是为了学习,请解释一下如何做。

这是我的尝试:

struct StructA {
    f: StructB
}

struct StructB {
    g: i32
}

impl From<StructB> for StructA {
    fn from(v: StructB) -> Self {
        Self {
            f: v
        }
    }
}

impl From<i32> for StructB {
    fn from(v: i32) -> Self {
        Self {
            g: v
        }
    }
}

impl<T: Into<StructA>, S: Into<T>> Into<StructA> for S {
    fn into(self) -> StructA {
        let i: T = self.into();
        i.into()
    }
}

我得到的错误是the type parameter 'T' is not constrained by the impl trait, self type, or predicates

我不明白。 T 不是受Into&lt;StructA&gt; 约束吗?

【问题讨论】:

  • 这是个坏主意,因为它破坏了 impl 的唯一性。例如,您可以引入另一种类型T0 : Into&lt;StructA&gt;, S: Into&lt;T0&gt;,现在您的一揽子快捷方式实现是模棱两可的。您可以通过关联类型实现类似的效果。

标签: generics rust polymorphism traits parametric-polymorphism


【解决方案1】:

据我所知,这在稳定的 Rust 上是不可能的,因为它需要某种形式的专业化。让我们考虑一下这段代码:

// "StructB -> StructA"
impl From<StructB> for StructA {
    fn from(v: StructB) -> Self {
        Self { f: v }
    }
}

impl From<i32> for StructB {
    fn from(v: i32) -> Self {
        Self { g: v }
    }
}

// "shortcut"
impl<T> From<T> for StructA
where
    StructB: From<T>,
{
    fn from(t: T) -> Self {
        Self::from(StructB::from(t))
    }
}

编译器会抱怨"StructB -&gt; StructA" impl 与"shortcut" impl 冲突。这是真的,因为 From&lt;T&gt; 有一个 blanket implementation 自己。

现在使用Into 根本没有帮助,因为Into 是自反的。所以即使你克服了“不受约束”的错误,你也会回到冲突的实现。

但是!可能在夜间。通过使用自动特征 (here's a nice article about it) 的一些小技巧,我们可以将“快捷方式”实现限制为仅涵盖不同于 StructB 的类型。

playground link

【讨论】:

  • 感谢您的回复! > 编译器会抱怨StructB -&gt; StructB impl 与“shortcut” impl 冲突。你的意思是StructB -&gt; StructA 对吗?因为我们的快捷方式是StructB -&gt; StructA
  • 是的,已修复。
【解决方案2】:

没有办法产生一个完全通用的解决方案,因为即使您通过“不受约束的类型参数”错误,您也会遇到“冲突的特征实现”错误。这是我所能得到的最接近的结果,它允许将StructB 转换为StructA 将任何可以转换为StructB 的类型转换为StructA

#[derive(Eq, PartialEq, Debug)]
struct StructA {
    f: StructB
}

#[derive(Eq, PartialEq, Debug)]
struct StructB {
    g: i32
}

impl From<i32> for StructB {
    fn from(v: i32) -> Self {
        Self {
            g: v
        }
    }
}

impl<T> From<T> for StructA
where
    T: Into<StructB>
{
    fn from(t: T) -> StructA {
        let structb = t.into();
        StructA {
            f: structb
        }
    }
}

fn main() {
    let a1: StructA = StructB { g: 12 }.into();
    let a2: StructA = 12.into();
    assert_eq!(a1, a2);
}

playground

【讨论】:

    【解决方案3】:

    错误消息基本上表明编译器无法推断 T 可能是什么类型 - 它基本上必须弄清楚是否存在任何类型 T 以便满足给定的特征边界 T: Into&lt;StructA&gt;, S: Into&lt;T&gt;,并且这在 Rust 中是不可能的。这样做的一个问题是,如 cmets 中所述,可能有多种类型 T 满足特征边界,在这种情况下编译器无法确定使用哪一种。

    此外,Into trait 已经在标准库中有一个全面的实现。

    impl<T, U: From<T>> Into<U> for T;
    

    编译器无法保证此 impl 不会与您的一揽子 impl 重叠,这也会使实现模棱两可。

    我建议您直接明确地实现From&lt;i32&gt; for StructA。如果您需要许多这样的实现,宏可能会很有用。

    【讨论】:

    • 感谢您的回复。我现在知道了。我想编译器无法知道/理解在我的情况下,每个S 只会有一个T 对吗?
    猜你喜欢
    • 2015-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-21
    • 1970-01-01
    • 2016-01-11
    • 1970-01-01
    相关资源
    最近更新 更多