Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Worse than lexical borrowing #36403

Closed
stepancheg opened this issue Sep 11, 2016 · 7 comments
Closed

Worse than lexical borrowing #36403

stepancheg opened this issue Sep 11, 2016 · 7 comments

Comments

@stepancheg
Copy link
Contributor

stepancheg commented Sep 11, 2016

fn get_stream_mut(s: &mut String) -> Option<&mut String> {
    unimplemented!()
}

fn new_request(s: &mut String) -> &mut String {
    unimplemented!()
}

fn get_or_create_stream(s: &mut String) -> &mut String {
    {
        match get_stream_mut(s) {
            Some(stream) => return stream,
            None => (),
        };
    };

    new_request(s)
}

Error is:

error[E0499]: cannot borrow `*s` as mutable more than once at a time
  --> src/main.rs:17:17
   |
11 |         match get_stream_mut(s) {
   |                              - first mutable borrow occurs here
...
17 |     new_request(s)
   |                 ^ second mutable borrow occurs here
18 | }
   | - first borrow ends here

On stable and nightly.

Curiously, it works fine without first return.

@Thiez
Copy link
Contributor

Thiez commented Sep 11, 2016

It's not curious at all, just the compiler typechecking correctly. Let's change the function to explicit lifetimes:

fn get_stream_mut<'a>(s: &'a mut String) -> Option<&'a mut String> {
    unimplemented!()
}
fn get_or_create_stream<'a>(s: &'a mut String) -> &'a mut String {
    {
        match get_stream_mut(s) {
            Some(stream) => return stream,
            None => (),
        };
    };
    new_request(s)
}

What is the type of stream? Well, we return it, so it must be &'a mut String. So what did we pass to get_mut_stream? It must have been a &'a mut String, because the function is fn<'a>(&'a mut String) -> Option<&'a mut String>. So the moment you call that function, you've lent out s for the entire duration of get_or_create_stream, no take backsies!

@stepancheg
Copy link
Contributor Author

stepancheg commented Sep 11, 2016

So the moment you call that function, you've lent out s for the entire duration of get_or_create_stream, no take backsies!

No, I've lent s to the end of the block.

Actually, I've found a kind of workaround. Which does the same with runtime overhead:

fn get_stream_mut(s: &mut String) -> Option<&mut String> {
    unimplemented!()
}

fn new_request(s: &mut String) -> &mut String {
    unimplemented!()
}

fn get_or_create_stream(s: &mut String) -> &mut String {
    {
        if get_stream_mut(s).is_some() {
            return get_stream_mut(s).unwrap()
        }
    };

    new_request(s)
}

It typechecks OK.

@Thiez
Copy link
Contributor

Thiez commented Sep 11, 2016

No, I've lent s to the end of the block.

But you didn't, because then stream couldn't live until the end of the block, which it does (because you return it). You lent it for 'a. Your workaround gets around this because the first call to get_stream_mut(s) lends s only for that statement, while the second one (where you return the result) once again lends out s for 'a. Try working out the types of all values in your original code and you'll notice why the borrow checker complains. Remember that types cannot change based on control flow.

@stepancheg
Copy link
Contributor Author

Sorry, I still don't understand.

Your workaround gets around this because the first call to get_stream_mut(s) lends s only for that statement, while the second one (where you return the result) once again lends out s for 'a.

If second call to get_stream_mut lends s for 'a, then why call to new_request does not complain?

@Thiez
Copy link
Contributor

Thiez commented Sep 11, 2016

Because the control flow ends at the return statement. The borrow checker sees that either you lend s for 'a to get_stream_mut, or you lend s for 'a to new_request. You workaround code kinda-sorta looks like this to the borrow checker:

`fn get_or_create_stream<'a>(s: &'a mut String) -> &'a mut String {
    let b: bool = { get_stream_mut(s).is_some() };
    if b {
        get_stream_mut(s)
    } else {
        new_request(s)
    }
}

Or, in other words, if some_condition { return something; } return something_else; is equivalent to return if some_condition { something } else { something_else };: clearly something and something_else cannot both be executed :)

@bluss
Copy link
Member

bluss commented Sep 12, 2016

It's a known limitation. It's discussed as part of Problem#3 in this blog post: Non-lexical Lifetimes: Introduction

It's part of rust-lang/rfcs/issues/811

@Mark-Simulacrum
Copy link
Member

I'm going to close since this is a known limitation and I don't think it's helpful to track this; with NLL this should be fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants