如果累加器既不是可复制类型也不是可变类型,是否可以在Rust中使用fold?

4

我正在尝试让一个折叠功能起作用,但遇到了生命周期问题。这是我代码的简化版本:

struct A {
  v: i32,
}
let v = vec![A { v: 1 }, A { v: 2 }, A { v: 3 }];
let max = v.iter().fold(None, |res: Option<A>, &item| { match res {
    Some(a) => if a.v >= item.v { Some(a) } else { Some(item) },
    None => Some(item)
}});

这会导致出现错误 error[E0507]: cannot move out of borrowed content
如果我按照之前的错误建议将闭包更改为 |res: Option<A>, ref item| …,则在 Some(item) 处会出现类型不匹配的错误,解决该引用后回到了原始错误。
我尝试在选项中使用引用,但是我遇到了生命周期问题:
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
 --> <anon>:6:67
  |
6 |   let maxV = v.iter().fold(None, |res: Option<&A>, &item| { match res {
  |                                                                   ^^^
  |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 6:58...
 --> <anon>:6:59
  |
6 |     let maxV = v.iter().fold(None, |res: Option<&A>, &item| { match res {
  |  ___________________________________________________________^ starting here...
7 | |       Some(a) => if a.v >= item.v { Some(a) } else { Some(&item) },
8 | |       None => Some(&item)
9 | |   }});
  | |____^ ...ending here
note: ...so that types are compatible (expected std::option::Option<&main::A>, found std::option::Option<&main::A>)
 --> <anon>:6:67
  |
6 |   let maxV = v.iter().fold(None, |res: Option<&A>, &item| { match res {
  |                                                                   ^^^
note: but, the lifetime must be valid for the method call at 6:13...
 --> <anon>:6:14
  |
6 |     let maxV = v.iter().fold(None, |res: Option<&A>, &item| { match res {
  |  ______________^ starting here...
7 | |       Some(a) => if a.v >= item.v { Some(a) } else { Some(&item) },
8 | |       None => Some(&item)
9 | |   }});
  | |_____^ ...ending here
note: ...so that argument is valid for the call
 --> <anon>:6:28
  |
6 |   let maxV = v.iter().fold(None, |res: Option<&A>, &item| { match res {
  |                            ^^^^

第一个版本可以通过添加#[derive (Copy, Clone)]使A成为复制类型来实现,但这并不总是可行的。
我尝试搜索rust中的折叠示例,但我要么找到了累加器是复制类型的示例(使用fold实现的i32求和),要么找到了累加器是容器且fold正在操作内容的示例(扩展向量或类似操作)。
我还发现了一个将值折叠到Option中的示例,但它不能匹配当前累加器的值。
我可以用for循环做到这一点,但更喜欢fold语法。
2个回答

3
你从Vec::iter()获得的迭代器会产生不可变的借用引用(在这种情况下为&A)到向量中的元素;向量在此之后将不会改变。
提醒一下,在Rust中默认情况下使用值移动它,使原始值无法使用;只有当你拥有该项时才能这样做,即不能从借用引用中移出。唯一的例外是使用Copy类型,这意味着它们足够简单,可以进行原始内存复制而不会使原始对象失效。
因此,对于任何非Copy类型,你都不能直接从不可变引用中赋值。
有几个选项。
首先,如果你可以实现或派生Clone(这意味着你提供了一个可以复制对象的方法,可能做更多的事情而不仅仅是原始复制),你可以显式克隆:
let max = v.iter().fold(None, |res: Option<A>, item| match res {
    Some(a) => if a.v >= item.v { Some(a) } else { Some(item.clone()) },
    None => Some(item.clone()),
});

你可以手动内联构造新项(Some(A { v: item.v })),但如果你这样做,最好派生或实现Clone
如果你可以消费(即销毁)你的项目向量以获得结果,那么你可以调用into_iter()方法而不是iter();在这种情况下,迭代器拥有项目(原始所有者Vec在此过程中被消耗),所以你可以移动它们。
let max = v.into_iter().fold(None, |res: Option<A>, item| match res {
    Some(a) => if a.v >= item.v { Some(a) } else { Some(item) },
    None => Some(item),
});

在这里移动工作正常,但是Vec不再存在。

无论如何,您需要制作副本(通过CopyClone或手动方式)或移动 - 这取决于您的应用程序,哪种更合适。


你能进一步解释一下为什么原始代码会出错吗?使用克隆/复制或使用into_iter()总是有解决方案的,但是否可以仅使用引用来解决问题? - basic_bgnr
我想在该项上获得另一个不可变引用(immutable borrow)。我一开始使用了Option <&A>,但是没有理解生命周期问题。整个问题的起因是编译器无法干涉 Some(a)a 的类型,我才意识到如果我将 None 匹配行放在第一位,则整个内容可以在没有类型注释的情况下工作,因为 None => Some(item) 将帮助编译器推断出与正确生命周期相关的匹配返回类型为 Option <&A> - gumininja

3

编译器存在几个关于闭包参数生命周期参数推断的错误;你在这里遇到的问题已经被报告为问题36867。在res: Option<&A>中,推断出来的&A的生命周期是错误的,这似乎是由返回类型(Option<&A>)也有一个生命周期参数引起的。解决方法是让编译器推断整个类型,这样可以正常工作,而不是在初始累加器值上给出类型提示(None):

#[derive(Debug)]
struct A {
    v: i32,
}

fn main() {
    let v = vec![A { v: 1 }, A { v: 2 }, A { v: 3 }];
    let max = v.iter().fold(None::<&A>, |res, item| { match res {
        Some(a) => if a.v >= item.v { Some(a) } else { Some(item) },
        None => Some(item)
    }});
    println!("{:?}", max);
}

或者关于max变量:
fn main() {
    let v = vec![A { v: 1 }, A { v: 2 }, A { v: 3 }];
    let max: Option<&A> = v.iter().fold(None, |res, item| { match res {
        Some(a) => if a.v >= item.v { Some(a) } else { Some(item) },
        None => Some(item)
    }});
    println!("{:?}", max);
}

奖励: 对于查找最大值的特定问题,您还可以使用 Iterator::max_by_key。无需注释!

fn main() {
    let v = vec![A { v: 1 }, A { v: 2 }, A { v: 3 }];
    let max = v.iter().max_by_key(|item| item.v);
    println!("{:?}", max);
}

你有这个 bug 的链接吗? - Matthieu M.
编辑了问题并找到了最接近的匹配。有很多针对闭包的错误报告,而且一些相互矛盾!例如,在问题24680中,你需要指定参数类型以避免编译器错误,这与此处的问题相反! - Francis Gagné
谢谢,原始代码中折叠后我有一个映射表,这就是为什么我无法在max上指定类型并且无法在None上执行它。现在它完美地工作了,max_by_key实际上是我需要的,我太过专注于折叠了。 - gumininja
正如我在另一个答案中所评论的,我刚刚意识到如果我首先匹配 None => Some(item),那么它可以帮助编译器正确推断累加器的类型,而我就不需要类型提示了。 - gumininja

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