【问题标题】:Why does the compiler prevent me from using push on a Vec created using collect()?为什么编译器阻止我在使用 collect() 创建的 Vec 上使用 push?
【发布时间】:2019-05-29 13:44:51
【问题描述】:

以下编译:

pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    let mut result = (0..list.len() - 1)
        .map(|i| format!("For want of a {} the {} was lost.", list[i], list[i + 1]))
        .collect::<Vec<String>>();
    result.push(format!("And all for the want of a {}.", list[0]));
    result.join("\n")
}

以下没有(see Playground):

pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    let mut result = (0..list.len() - 1)
        .map(|i| format!("For want of a {} the {} was lost.", list[i], list[i + 1]))
        .collect::<Vec<String>>()
        .push(format!("And all for the want of a {}.", list[0]))
        .join("\n");
    result
}

编译器告诉我

error[E0599]: no method named `join` found for type `()` in the current scope
 --> src/lib.rs:9:10
  |
9 |         .join("\n");
  |          ^^^^

如果我尝试仅使用 push 进行撰写,我会遇到相同类型的错误。

我希望collect 返回B,也就是Vec&lt;String&gt;Vec 不是()Vec 当然有我想包含在组合函数列表中的方法。

为什么我不能编写这些函数?解释可能包括描述在collect() 之后终止表达式的“魔力”,以使编译器以我使用push 等编写时不会发生的方式实例化Vec

【问题讨论】:

  • 因为push() 不返回向量

标签: rust collect


【解决方案1】:

如果您阅读documentation for Vec::push 并查看方法的签名,您会发现它不会返回Vec

pub fn push(&mut self, value: T)

由于没有明确的返回类型,返回类型是单位类型()() 上没有名为 join 的方法。您需要多行编写代码。

另见:


我会写得更实用:

use itertools::Itertools; // 0.8.0

pub fn build_proverb(list: &[&str]) -> String {
    let last = list
        .get(0)
        .map(|d| format!("And all for the want of a {}.", d));

    list.windows(2)
        .map(|d| format!("For want of a {} the {} was lost.", d[0], d[1]))
        .chain(last)
        .join("\n")
}

fn main() {
    println!("{}", build_proverb(&["nail", "shoe"]));
}

另见:

【讨论】:

  • 谢谢。如果result.push(...) 有效,是什么阻止我用构建Vec&lt;String&gt; 的表达式的乘积替换result?答案与@stargateur 和您的答案地址完全相同。然而,push 不返回Vec 可能不是更重要,而是push 需要对Vec 的可变引用?说了这么多,也许在编写函数时我需要注意一个转变:以Iterable 作为参数的函数,这些不是特征,而是特定于结构的方法,例如push。对吗?
  • @Edmund'sEcho 你可以做到that
  • 太棒了。谢谢!我还有更多工作要做,以了解我对此的看法。
  • 我的 FP 出了什么问题:要像我一样组合函数,它们需要“纯”; “纯”允许突变,我们只需要引用透明性。我误入歧途,将result.push(...) 中的resultlet mut result =... 中的result 联系起来。缺陷:在result.push(...) 中,push 从输入角度((self, newValue))看起来“纯”,但实际上是“不纯的”只是,因为无论输入如何,它都会返回相同的输出。所以,我不能用push 作曲。混淆了我的困惑,构建一个可变引用以使其指向“not ()”。
  • @Edmund'sEcho "push 从输入角度看起来很“纯粹”((self, newValue))" 这里的有效输入角度是(&amp;mut self, newValue),这清楚地表明它可以修改self,所以很可能不是纯的。
【解决方案2】:

感谢大家的有益互动。前面的回复中所说的一切都是正确的。而且,当我学习 Rust 时,还有一个更大的图景。

来自 Haskell(几年前接受过 C 培训),我遇到了 OO 方法链接方法,该方法使用指针在方法调用之间进行链接;不需要纯函数(即,我使用let mut result = ... 所做的事情,然后使用/需要使用pushresult.push(...) 中更改Vec 的值)。我认为更普遍的观察是,在 OO 中,返回 unit 是“正常的”,因为方法链接不需要返回值。

下面的自定义代码将push 定义为特征;它使用与“OO”push 相同的输入,但返回更新后的self。也许只是作为一个侧面评论,这使得函数纯粹(输出取决于输入)但在实践中,这意味着定义为特征的 push 使我期望的函数的 FP 组合成为一种规范(我认为足够公平首先考虑到 Rust 从 Haskell 借了多少)。

@Stargateur、@E_net4 和@Shepmaster 提出的代码解决方案捕捉到了我试图完成的事情以及问题的核心。只有最小的编辑如下: (见playground

pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    list.windows(2)
        .map(|d| format!("For want of a {} the {} was lost.", d[0], d[1]))
        .collect::<Vec<_>>()
        .push(format!("And all for the want of a {}.", list[0]))
        .join("\n")
}

解决方案要求我将push 定义为返回self 的特征, 在这种情况下键入 Vec

trait MyPush<T> {
    fn push(self, x: T) -> Vec<T>;
}

impl<T> MyPush<T> for Vec<T> {
    fn push(mut self, x: T) -> Vec<T> {
        Vec::push(&mut self, x);
        self
    }
}

最后观察,在调查许多 Rust 特征时,我找不到返回 () 的特征函数(例如,Write 返回 Result ())。

这与我在这里学到的 structenum methods 的预期形成鲜明对比。 trait 和 OO 方法都可以访问self,因此都被描述为“方法”,但似乎存在一个值得注意的内在差异:OO 方法使用引用来启用顺序更改self,FP 特征(如果你愿意的话)使用依赖于使用“纯”状态改变函数的函数组合来完成相同的任务(:: (self, newValue) -&gt; self)。

也许顺便说一句,在这种情况下,Haskell 通过创建新副本(模数幕后优化)实现了引用透明性,Rust 似乎通过管理所有权在自定义 trait 代码中完成了类似的事情(转移到 trait 函数,并返回self)。

“组合函数”难题的最后一块:要使组合起作用,一个函数的输出需要具有下一个函数输入所需的类型。 join 在我向它传递一个值和向它传递一个引用时都起作用(对于实现 IntoIterator 的类型来说是真的)。所以join 似乎有能力在编程的方法链和函数组合风格中工作。

不依赖于返回值的 OO 方法和 Rust 中通常为 true 的特征之间的区别是否存在? “这里和那里”似乎就是这种情况。例如,与线条清晰的push 相比,join 似乎正在成为标准库的一部分,该标准库被定义为SliceConcatExt 的方法和SliceConcatExt 的特征函数( see rust srcthe rust-lang issue discussion)。下一个问题,统一标准库中的方法是否符合 Rust 的设计理念? (只需为您使用的安全、高性能、富有表现力和使用乐趣付费)

【讨论】:

    猜你喜欢
    • 2020-04-07
    • 1970-01-01
    • 1970-01-01
    • 2016-07-06
    • 1970-01-01
    • 2019-04-19
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    相关资源
    最近更新 更多