Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How are return values of type `impl Trait` borrow-checked?

The following code fails to compile:

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

It compiles if I replace foo's signature with

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

It makes me believe that impl Trait types are borrow-checked more conservatively: the compiler assumes that the returned object captures fun even though it doesn't.

However, this interesting example compiles fine:

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);
    }
}

Here the compiler seems not to assume that the returned impl Iterator<Item = i32> borrows s.

How exactly are returned impl Trait types borrow-checked? When are they assumed to borrow other function arguments, like in the first case? When are they assumed not to, like in the latter case?

like image 579
rubo Avatar asked Jun 26 '21 15:06

rubo


1 Answers

I believe this issue comment tells the story here. It sounds like it's an intentional limitation of the type system to be conservative, but I agree with the issue author that this would be good to be able to opt out of:

The core reason for this behaviour is the variance behaviour of impl Trait in return position: the returned impl Trait is always variant over all generic input parameters, even if not technically used. This is done so that if you change the internal implementation of a public API returning impl Trait you don't have to worry about introducing an additional variance param to the API. This could break downstream code and is thus not desireable for Rust's semver system.

like image 170
Daniel Wagner-Hall Avatar answered Nov 19 '22 19:11

Daniel Wagner-Hall