编译器为什么声称来自高阶trait约束的关联类型未实现“Display”,尽管它应该实现?

12

我正在构建一个实现字符串拼接的库,也就是将容器中所有元素用分隔符隔开打印出来。我的基本设计如下:

use std::fmt;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Join<Container, Sep> {
    container: Container,
    sep: Sep,
}

impl<Container, Sep> fmt::Display for Join<Container, Sep>
where
    for<'a> &'a Container: IntoIterator,
    for<'a> <&'a Container as IntoIterator>::Item: fmt::Display,
    Sep: fmt::Display,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut iter = self.container.into_iter();

        match iter.next() {
            None => Ok(()),
            Some(first) => {
                first.fmt(f)?;

                iter.try_for_each(move |element| {
                    self.sep.fmt(f)?;
                    element.fmt(f)
                })
            }
        }
    }
}

这个特性实现可以顺利编译。请注意&'a C: IntoIterator的约束条件。许多容器为了允许对包含的项目的引用进行迭代(例如,Vec这里实现了它),会为自己的引用实现IntoIterator

然而,当我尝试使用我的Join结构体时,我得到了一个无法满足的特性约束:

fn main() {
    let data = vec!["Hello", "World"];
    let join = Join {
        container: data,
        sep: ", ",
    };
    println!("{}", join);
}

这段代码会产生编译错误:

error[E0277]: `<&'a std::vec::Vec<&str> as std::iter::IntoIterator>::Item` doesn't implement `std::fmt::Display`
  --> src/main.rs:38:20
   |
38 |     println!("{}", join);
   |                    ^^^^ `<&'a std::vec::Vec<&str> as std::iter::IntoIterator>::Item` cannot be formatted with the default formatter
   |
   = help: the trait `for<'a> std::fmt::Display` is not implemented for `<&'a std::vec::Vec<&str> as std::iter::IntoIterator>::Item`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: required because of the requirements on the impl of `std::fmt::Display` for `Join<std::vec::Vec<&str>, &str>`
   = note: required by `std::fmt::Display::fmt`

关键句似乎是这句话:

the trait `for<'a> std::fmt::Display` is not implemented for `<&'a std::vec::Vec<&str> as std::iter::IntoIterator>::Item`

很不幸,编译器实际上没有告诉我Item类型是什么,但根据我阅读文档的理解,它似乎是&T,在这种情况下意味着&&str
为什么编译器认为&&str没有实现Display?我尝试了许多其他类型,比如usizeString,它们都无法工作;他们都以相同的错误失败。我知道这些引用类型不直接实现Display,但实现应该通过deref coercion自动选择,对吗?

1
在我看来,这确实是编译器的一个缺陷。您是正确的,项目类型是 &&str,您可以通过尝试编译 let _: <&Vec<&str> as IntoIterator>::Item = (); 来轻松验证。而且 &&str 绝对 实现了 Display,可以通过将 <&&str as fmt::Display>::fmt(&&"hello", f)?; 添加到 fmt() 的主体中进行验证。所以这应该绝对可行。我猜测 <&'a std::vec::Vec<&str> as std::iter::IntoIterator>::Item 由于某种原因没有正确规范化为 &'a &str - Sven Marnach
1
请参阅 https://github.com/rust-lang/rust/issues/24159 以及其中链接的问题。通常可以通过为项目类型添加一个额外的类型参数并将其强制为实际项目类型来解决此问题,方法是将 trait 约束 IntoIterator 更改为 IntoIterator<Item = T>,其中 T 是新的类型参数。但由于 HRTBs 的原因,在这种情况下无法使用此方法。 - Sven Marnach
在这种情况下,您实际上可以这样做;我尝试过 <T: Display> ... where &'a C: IntoIterator<Item=T>。然而,我得到了一个不同的、有点更少用的关于未满足生命周期限制的错误信息。 - Lucretiel
1
@Lucretiel,这不起作用的原因是因为Item的生命周期。在您的示例中,项目类型是&&str,但是如果注释HRTB的生命周期,则必须将其设置为对于所有生命周期 'a都是&'a &str,这当然是不可能的。这就是我在之前评论中所说的。 - Sven Marnach
1
原始代码现在可以在Nightly(1.56及以上版本)中编译。 - trent
显示剩余2条评论
1个回答

8

看起来像是编译器的限制。目前您可以通过编写以私有辅助 trait 表示“具有生命周期的显示”来解决它。这使得编译器能够看到for<'a> private::Display<'a>意味着fmt :: Display

use std::fmt;

pub struct Join<Container, Sep> {
    container: Container,
    sep: Sep,
}

mod private {
    use std::fmt;
    pub trait Display<'a>: fmt::Display {}
    impl<'a, T> Display<'a> for T where T: fmt::Display {}
}

impl<Container, Sep> fmt::Display for Join<Container, Sep>
where
    for<'a> &'a Container: IntoIterator,
    for<'a> <&'a Container as IntoIterator>::Item: private::Display<'a>,
    Sep: fmt::Display,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut iter = self.container.into_iter();

        match iter.next() {
            None => Ok(()),
            Some(first) => {
                first.fmt(f)?;

                iter.try_for_each(move |element| {
                    self.sep.fmt(f)?;
                    element.fmt(f)
                })
            }
        }
    }
}

fn main() {
    println!(
        "{}",
        Join {
            container: vec!["Hello", "World"],
            sep: ", ",
        }
    );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接