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

Allow for borrowing during use if guaranteed immovability #2337

Closed
Nokel81 opened this issue Feb 15, 2018 · 6 comments
Closed

Allow for borrowing during use if guaranteed immovability #2337

Nokel81 opened this issue Feb 15, 2018 · 6 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@Nokel81
Copy link
Contributor

Nokel81 commented Feb 15, 2018

Motivation:

Currently you cannot borrow a value mutable why it is currently being borrowed. This is a core aspect of Rust and should not be removed, however, there are legitimate uses for wanting to do something like this and with the proposal this would become more easy to accomplish safely.

Example (of what is wanted to be done):

#[derive(PartialEq)]
struct Actor {
    hp: i32,
    attacking: bool,
}

impl Actor {
    pub fn new() -> Actor {
        Actor {
            hp: 10,
            attacking: false,
        }
    }
    
    pub fn damage(&mut self) {
        self.hp -= 1;
    }
}

fn main() {
    let mut actors = vec![Actor::new(), Actor::new()];
    actors[1].attacking = true;
    
    for actor in actors.iter_mut() {
        //if an actor is attacking, damage all the other actors
        if actor.attacking {
            //doesnt compile due to two mut borrows
            for actor2 in actors.iter_mut() {
                if actor2 != actor {
                    actor2.damage();
                }
            }
        }
    }
}

This was taken from here.

There are ways to work around this currently but generally it requires convoluted methods or extra allocations which are undesirable especially for people coming to rust.

A working example that leaves most of the algorithm the same has the following main, gotten from here:

fn main() {
    let actors = UnsafeCell::new(vec![Actor::new(), Actor::new()]);
    unsafe {
        (*actors.get())[1].attacking = true;
    }

    for actor in unsafe { (*actors.get()).iter_mut() } {
        //if an actor is attacking, damage all the other actors
        if actor.attacking {
            for actor2 in unsafe { (*actors.get()).iter_mut() } {
                if actor2 != actor {
                    actor2.damage();
                }
            }
        }
    }
}

Proposition:

A new type of borrowing, called immovable borrow which can be done at any time, even when another item has borrowed it. This would only allow mutations that are guaranteed not to require a move. These would only be allowed with mutable borrows. To my knowledge most actions on intrinsics are no-move but there may be others.

This would be implemented by two item: iter_mut_immov() which would be for generated mutable immovable references for iterating through and immov which would be a keyword use in conjunction with &mut (ie &mut immov) for getting these sorts of borrows individually.

An error that recommends this type of borrow would also be implemented.

This proposal does ask for adding a new keyword or at least a new contextual keyword (then it would be &immov mut)

Example:
Using this proposition the following would be done:

fn main() {
    let mut actors = vec![Actor::new(), Actor::new()];
    actors[1].attacking = true;
    
    for actor in actors.iter() {
        //if an actor is attacking, damage all the other actors
        if actor.attacking {
            //now with immovable borrow
            for actor2 in actors.iter_mut_immov() {
                if actor2 != actor {
                    actor2.damage();
                }
            }
        }
    }
}
@scottmcm
Copy link
Member

Mutations are still a problem even if it's immovable and single-threaded:
https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/

@Nokel81
Copy link
Contributor Author

Nokel81 commented Feb 15, 2018

Thank you very much for the article, good read.

However, I would argue that the enum example that he talked about counts as moving so would not be allowed with such a borrow

@scottmcm
Copy link
Member

counts as moving

I'm not sure what you mean by that. If the "immovable mutable borrow" doesn't let you assign to a field of the borrowed thing, how is it mutable at all?

@Nokel81
Copy link
Contributor Author

Nokel81 commented Feb 19, 2018

It counts as moving if the change would invalidate a interator pointing at it.

As to mutable, this is to be a restricted form so that so rust is easier to use is some use cases

@Ixrec
Copy link
Contributor

Ixrec commented Feb 19, 2018

Isn't it possible for mutation to invalidate iterators even without moving? I think you could make it safe by assuming the iterator's referent contains no references of its own, but that rules out even the basic use case of iterating over collections.

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Feb 19, 2018
@Nokel81
Copy link
Contributor Author

Nokel81 commented Feb 19, 2018

It is possible that some mutations invalidate iterators but not all of them. If you have a vec of items that includes a vec then pushing to that would not be allowed but writing to some i32 field would

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

4 participants