Skip to content

How can let y; and let y: _; be different to the borrow checker? #138194

Open
@steffahn

Description

@steffahn

@rustbot label A-borrow-checker, C-discussion

I’ve tested across this code example when trying to understand corner-cases of borrow checker behavior:

fn test() {
    let x = Box::new(1);
    let y = &(&*x, 123);
    let r = &y.1;
    drop(x);
    println!("{r}");
}

The fact that this does work in the first place is slightly cool; given that the type of y, &'a (&'b i32, i32) would usually come with an 'b: 'a bound, but the rest of the code make it so that the re-borrow in r (which could have lifetime &'a i32) lives longer than the Box that the inner &'b i32 comes from.

But then I noticed – slightly disappointed – that

fn test() {
    let x = Box::new(1);
    let y: _ = &(&*x, 123);
    let r = &y.1;
    drop(x);
    println!("{r}");
}

no longer works! Now, I first thought… such type annotations can sometimes reasonably have significant effects on the meaning of the code, in particular some let y: &_ = …; usually starts allowing y to be created through a reborrow … at least for &mut T that’s sometimes very relevant.

But here, I’m not actually providing any concrete type in the first place. So why would the added : _ still make any difference?

For a slight variation with the same effect, these two have the same distinction:

fn test() {
    let x = Box::new(1);
    let y_target = (&*x, 123);
    let y;
    y = &y_target;
    let r = &y.1;
    drop(x);
    println!("{r}");
}

behavior:
compiles successfully


vs

fn test() {
    let x = Box::new(1);
    let y_target = (&*x, 123);
    let y: _;
    y = &y_target;
    let r = &y.1;
    drop(x);
    println!("{r}");
}

behavior:

error[E0505]: cannot move out of `x` because it is borrowed
 --> src/lib.rs:7:10
  |
2 |     let x = Box::new(1);
  |         - binding `x` declared here
3 |     let y_target = (&*x, 123);
  |                     --- borrow of `*x` occurs here
...
7 |     drop(x);
  |          ^ move out of `x` occurs here
8 |     println!("{r}");
  |               --- borrow later used here
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 -     let y_target = (&*x, 123);
3 +     let y_target = (&x.clone(), 123);
  |

(playground)

It seems surprising that let y; and let y: _; aren’t fully equivalent; in my mind, they should simply both leave the type of y to-be-inferred for later. [Still, I’d also suspect that “implementation details of type inference” + “interaction of type information with the borrow checker” somehow forms the answer to this.]

I’m leaving this with just C-discussion for now; I haven’t found this reported elsewhere here on the rust-lang/rust issue tracker (as far as the search bar & labels could tell me). If someone else deems this to be a bug and/or well-fixable, we can turn it into C-bug as well 😇

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-borrow-checkerArea: The borrow checkerC-discussionCategory: Discussion or questions that doesn't represent real issues.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions