If you can construct the new value independently of the old, sure. But if 
constructing the new value
requires consuming the old, then you can't.

-Kevin

On Feb 25, 2014, at 3:14 PM, Corey Richardson <[email protected]> wrote:

> Is this not already expressible with swap/replace? Is there a big
> improvement here that I'm missing?
> 
> On Tue, Feb 25, 2014 at 4:23 PM, Kevin Ballard <[email protected]> wrote:
>> I too was under the impression that you could not read from a 
>> mutably-borrowed location.
>> 
>> I am looking forward to the ability to move out of a &mut (as long as the 
>> value is replaced again),
>> if the issues around task failure and destructors can be solved.
>> 
>> -Kevin
>> 
>> On Feb 25, 2014, at 12:19 PM, Michael Woerister <[email protected]> 
>> wrote:
>> 
>>> I'm all for it. In fact,  I thought the proposed new rules *already* where 
>>> the case :-)
>>> 
>>> On 25.02.2014 19:32, Niko Matsakis wrote:
>>>> I wrote up an RFC. Posted on my blog at:
>>>> 
>>>> http://smallcultfollowing.com/babysteps/blog/2014/02/25/rust-rfc-stronger-guarantees-for-mutable-borrows/
>>>> 
>>>> Inlined here:
>>>> 
>>>> Today, if you do a mutable borrow of a local variable, you lose the
>>>> ability to *write* to that variable except through the new reference
>>>> you just created:
>>>> 
>>>>    let mut x = 3;
>>>>    let p = &mut x;
>>>>    x += 1;  // Error
>>>>    *p += 1; // OK
>>>>    However, you retain the ability to *read* the original variable:
>>>> 
>>>>    let mut x = 3;
>>>>    let p = &mut x;
>>>>    print(x);  // OK
>>>>    print(*p); // OK
>>>>    I would like to change the borrow checker rules so that both writes
>>>> and reads through the original path `x` are illegal while `x` is
>>>> mutably borrowed. This change is not motivated by soundness, as I
>>>> believe the current rules are sound. Rather, the motivation is that
>>>> this change gives strong guarantees to the holder of an `&mut`
>>>> pointer: at present, they can assume that an `&mut` referent will not
>>>> be changed by anyone else.  With this change, they can also assume
>>>> that an `&mut` referent will not be read by anyone else. This enable
>>>> more flexible borrowing rules and a more flexible kind of data
>>>> parallelism API than what is possible today. It may also help to
>>>> create more flexible rules around moves of borrowed data. As a side
>>>> benefit, I personally think it also makes the borrow checker rules
>>>> more consistent (mutable borrows mean original value is not usable
>>>> during the mutable borrow, end of story). Let me lead with the
>>>> motivation.
>>>> 
>>>> ### Brief overview of my previous data-parallelism proposal
>>>> 
>>>> In a previous post I outlined a plan for
>>>> [data parallelism in Rust][dp] based on closure bounds. The rough idea
>>>> is to leverage the checks that the borrow checker already does for
>>>> segregating state into mutable-and-non-aliasable and
>>>> immutable-but-aliasable. This is not only the recipe for creating
>>>> memory safe programs, but it is also the recipe for data-race freedom:
>>>> we can permit data to be shared between tasks, so long as it is
>>>> immutable.
>>>> 
>>>> The API that I outlined in that previous post was based on a `fork_join`
>>>> function that took an array of closures. You would use it like this:
>>>> 
>>>>    fn sum(x: &[int]) {
>>>>        if x.len() == 0 {
>>>>            return 0;
>>>>        }
>>>>                 let mid = x.len() / 2;
>>>>        let mut left = 0;
>>>>        let mut right = 0;
>>>>        fork_join([
>>>>            || left = sum(x.slice(0, mid)),
>>>>            || right = sum(x.slice(mid, x.len())),
>>>>        ]);
>>>>        return left + right;
>>>>    }
>>>>    The idea of `fork_join` was that it would (potentially) fork into N
>>>> threads, one for each closure, and execute them in parallel. These
>>>> closures may access and even mutate state from the containing scope --
>>>> the normal borrow checker rules will ensure that, if one closure
>>>> mutates a variable, the other closures cannot read or write it. In
>>>> this example, that means that the first closure can mutate `left` so
>>>> long as the second closure doesn't touch it (and vice versa for
>>>> `right`). Note that both closures share access to `x`, and this is
>>>> fine because `x` is immutable.
>>>> 
>>>> This kind of API isn't safe for all data though. There are things that
>>>> cannot be shared in this way. One example is `Cell`, which is Rust's
>>>> way of cheating the mutability rules and making a value that is
>>>> *always* mutable. If we permitted two threads to touch the same
>>>> `Cell`, they could both try to read and write it and, since `Cell`
>>>> does not employ locks, this would not be race free.
>>>> 
>>>> To avoid these sorts of cases, the closures that you pass to to
>>>> `fork_join` would be *bounded* by the builtin trait `Share`. As I
>>>> wrote in [issue 11781][share], the trait `Share` indicates data that
>>>> is threadsafe when accessed through an `&T` reference (i.e., when
>>>> aliased).
>>>> 
>>>> Most data is sharable (let `T` stand for some other sharable type):
>>>> 
>>>> - POD (plain old data) types are forkable, so things like `int` etc.
>>>> - `&T` and `&mut T`, because both are immutable when aliased.
>>>> - `~T` is sharable, because is is not aliasable.
>>>> - Structs and enums that are composed of sharable data are sharable.
>>>> - `ARC`, because the reference count is maintained atomically.
>>>> - The various thread-safe atomic integer intrinsics and so on.
>>>> 
>>>> Things which are *not* sharable include:
>>>> 
>>>> - Many types that are unsafely implemented:
>>>>  - `Cell` and `RefCell`, which have non-atomic interior mutability
>>>>  - `Rc`, which uses non-atomic reference counting
>>>> - Managed data (`Gc<T>`) because we do not wish to
>>>>  maintain or support a cross-thread garbage collector
>>>> 
>>>> There is a wrinkle though. With the *current* borrow checker rules,
>>>> forkable data is only safe to access from a parallel thread if the
>>>> *main thread* is suspended. Put another way, forkable closures can
>>>> only run concurrently with other forkable closures, but not with the
>>>> parent, which might not be a forkable thing.
>>>> 
>>>> This is reflected in the API, which consisted of a function
>>>> `fork_join` function that both spawned the threads and joined them.
>>>> The natural semantics of a function call would thus cause the parent
>>>> to block while the threads executed. For many use cases, this is just
>>>> fine, but there are other cases where it's nice to be able to fork off
>>>> threads continuously, allowing the parent to keep running in the
>>>> meantime.
>>>> 
>>>> *Note:* This is a refinement of the [previous proposal][dp], which was
>>>> more complex. The version presented here is simpler but equally
>>>> expressive. It will work best when combined with my (ill documented,
>>>> that's coming) plans for [unboxed closures][8622], which are required
>>>> to support convenient array map operations and so forth.
>>>> 
>>>> ### A more flexible proposal
>>>> 
>>>> If we made the change that I described above -- that is, we prohibit
>>>> reads of data that is mutably borrowed -- then we could adjust the
>>>> `fork_join` API to be more flexible. In particular, we could support
>>>> an API like the following:
>>>> 
>>>>    fn sum(x: &[int]) {
>>>>        if x.len() == 0 {
>>>>            return 0;
>>>>        }
>>>>                 let mid = x.len() / 2;
>>>>        let mut left = 0;
>>>>        let mut right = 0;
>>>>                 fork_join_section(|sched| {
>>>>            sched.fork(|| left = sum(x.slice(0, mid)));
>>>>            sched.fork(|| right = sum(x.slice(mid, x.len())));
>>>>        });
>>>>                 return left + right;
>>>>    }
>>>> 
>>>> The idea here is that we replaced the `fork_join()` call with a call
>>>> to `fork_join_section()`. This function takes a closure argument and
>>>> passes it a an argument `sched` -- a scheduler. The scheduler offers a
>>>> method `fork` that can be invoked to fork off a potentially parallel
>>>> task. This task may begin execution immediately and will be joined
>>>> once the `fork_join_section` ends.
>>>> 
>>>> In some sense this is just a more verbose replacement for the previous
>>>> call, and I imagine that the `fork_join()` function I showed
>>>> originally will remain as a convenience function. But in another sense
>>>> this new version is much more flexible -- it can be used to fork off
>>>> any number of tasks, for example, and it permits the main thread to
>>>> continue executing while the fork runs.
>>>> 
>>>> *An aside:* it should be noted that this API also opens the door
>>>> (wider) to a kind of anti-pattern, in which the main thread quickly
>>>> enqueues a ton of small tasks before it begins to operate on
>>>> them. This is the opposite of what (e.g.) Cilk would do. In Cilk, the
>>>> processor would immediately begin executing the forked task, leaving
>>>> the rest of the "forking" in a stealable thunk. If you're lucky, some
>>>> other proc will come along and do the forking for you. This can reduce
>>>> overall overhead. But anyway, this is fairly orthogonal.
>>>> 
>>>> ### Beyond parallelism
>>>> 
>>>> The stronger guarantee concerning `&mut` will be useful in other
>>>> scenarios. One example that comes to mind are moves: for example,
>>>> today we do not permit moves out of borrowed data. In principle,
>>>> though, we should be able to permit moves out of `&mut` data, so long
>>>> as the value is replaced before anyone can read it.
>>>> 
>>>> Without the rule I am proposing here, though, it's really hard to
>>>> prevent reads at all without tracking what pointers point at (which we
>>>> do not do nor want to do, generally). Consider even a simple program
>>>> like the following:
>>>> 
>>>> ```
>>>> let x = ~3;
>>>> let y = &mut x;
>>>> let z = *y;     // Moves out of `*y` (and `*x`, therefore)
>>>> let _ = *x;     // Error! `*x` is invalid.
>>>> *y = ~5;        // Replaces `*y`
>>>> ```
>>>> 
>>>> I don't want to dive into the details of moves here, because
>>>> permitting rules from borrowed pointers is a complex topic of its own
>>>> (we must consider, for example, failure and what happens when
>>>> destructors run). But without the proposal here, I think we can't even
>>>> get started.
>>>> 
>>>> Speaking more generally and mildly more theoretically, this rule helps
>>>> to align Rust logic with separation logic. Effectively, `&mut`
>>>> references are known to be separated from the rest of the heap. This is
>>>> similar to what research languages like [Mezzo][m] do. (By the way,
>>>> if you are not familiar with Mezzo, check it out. Awesome stuff.)
>>>> 
>>>> ### Impact on existing code
>>>> 
>>>> It's hard to say what quantity of existing code relies on the current
>>>> rules. My gut tells me "not much" but without implementing the change
>>>> I can't say for certain.
>>>> 
>>>> ### How to implement
>>>> 
>>>> Implementing this rule requires a certain amount of refactoring in the
>>>> borrow checker (refactoring that is needed for other reasons as well,
>>>> however). In the interest of actually completing this blog post, I'm
>>>> not going to go into more details (the post has been sitting for some
>>>> time waiting for me to have time to write this section). If you think
>>>> you might like to implement this change, though, let me know. =)
>>>> 
>>>> [dp]: 
>>>> http://smallcultfollowing.com/babysteps/blog/2013/06/11/data-parallelism-in-rust/
>>>> [share]: https://github.com/mozilla/rust/issues/11781#issuecomment-35559695
>>>> [8622]: https://github.com/mozilla/rust/issues/8622
>>>> [m]: http://protz.github.io/mezzo/
>>>> _______________________________________________
>>>> Rust-dev mailing list
>>>> [email protected]
>>>> https://mail.mozilla.org/listinfo/rust-dev
>>> 
>>> _______________________________________________
>>> Rust-dev mailing list
>>> [email protected]
>>> https://mail.mozilla.org/listinfo/rust-dev
>> 
>> 
>> _______________________________________________
>> Rust-dev mailing list
>> [email protected]
>> https://mail.mozilla.org/listinfo/rust-dev
>> 

Attachment: smime.p7s
Description: S/MIME cryptographic signature

_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to