`impl Trait`类型的返回值如何进行借用检查?

7
以下代码无法编译:
fn foo<'a, F: Fn() -> &'a str>(vec: Vec<i32>, fun: F) -> impl Iterator<Item = i32> {
    println!("{}", fun());
    vec.into_iter()
}

fn main() {
    let s = "hello, world!".to_string();
    let iter = foo(vec![1, 2, 3], || &s);
    drop(s);

    for x in iter {
        println!("{}", x);
    }
}

error[E0505]: cannot move out of `s` because it is borrowed
  --> src/main.rs:9:10
   |
8  |     let iter = foo(vec![1, 2, 3], || &s);
   |                                   --  - borrow occurs due to use in closure
   |                                   |
   |                                   borrow of `s` occurs here
9  |     drop(s);
   |          ^ move out of `s` occurs here
10 | 
11 |     for x in iter {
   |              ---- borrow later used here

如果我将foo的声明替换为以下内容,它将被编译。

fn foo<'a, F: Fn() -> &'a str>(vec: Vec<i32>, fun: F) -> <Vec<i32> as IntoIterator>::IntoIter {
    // ...
}

这让我相信impl Trait类型的借用检查更加保守:即使实际上没有捕获fun,编译器也会假设返回的对象捕获了它。

然而,这个有趣的例子可以通过编译:

fn foo(s: &str) -> impl Iterator<Item = i32> {
    println!("{}", s);
    vec![1, 2, 3].into_iter()
}

fn main() {
    let s = "hello, world!".to_string();
    let iter = foo(&s);
    drop(s);

    for x in iter {
        println!("{}", x);
    }
}

在这里,编译器似乎并不认为返回的 impl Iterator<Item = i32> 借用了 s

返回的impl Trait类型是如何进行借用检查的?什么情况下假定它们借用其他函数参数(比如第一个示例)?什么情况下假定它们不借用其他参数(比如后面的示例)?


我本以为 -> impl 'static + Iterator<Item=i32> 在这里会有所作为,但实际上并没有。 - eggyal
1个回答

3
我认为这篇问题评论讲述了整个故事。看起来这是类型系统有意限制的一种保守做法,但我同意问题作者的观点,希望能够选择性地取消此限制。

这种行为的核心原因是impl Trait在返回位置上的变化行为:即使没有在技术上使用,返回的impl Trait始终对所有通用输入参数具有差异性。 这样做是为了使如果您更改返回impl Trait的公共API的内部实现,您无需担心向API引入附加的差异参数。 这可能会破坏下游代码,因此不符合Rust的semver系统。


我想要的答案是:“返回的 impl Trait 在所有泛型输入参数上始终是 variant 的,即使实际上没有使用。这样做是为了如果您更改返回 impl Trait 的公共 API 的内部实现,您不必担心向 API 引入额外的 variance 参数。” - rubo

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