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

Add non-unsafe .get_mut() for Unsafecell #76936

Merged
merged 2 commits into from
Sep 21, 2020

Conversation

danielhenrymantilla
Copy link
Contributor

@danielhenrymantilla danielhenrymantilla commented Sep 19, 2020

As discussed in: https://internals.rust-lang.org/t/add-non-unsafe-get-mut-for-unsafecell/12407

This PR tries to move the sound &mut UnsafeCell<T> -> &mut T projection that all the "downstream" constructions were already relying on, up to the root abstraction, where it rightfully belongs, and officially blessing it.

  • this helps reduce the amount of unsafe snippets out there (c.f., the second commit of this PR: 5886c38)

The fact that this getter is now expose for UnsafeCell<T> itself, will also help convey the idea that UnsafeCell is not magical w.r.t. &mut accesses, contrary to what some people incorrectly think.

  • Even the standard library itself at some point had such a confusion, c.f. this comment where there is a mention of multi-threaded (and thus shared) access despite dealing with exclusive references over unique ownership:
    pub fn get_mut(&mut self) -> &mut T {
    // SAFETY: This can cause data races if called from a separate thread,

r? @RalfJung

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Sep 19, 2020
@danielhenrymantilla danielhenrymantilla force-pushed the unsafecell_get_mut branch 2 times, most recently from 7dca86f to 09503fd Compare September 19, 2020 20:19
@RalfJung
Copy link
Member

Even the standard library itself at some point had such a confusion, c.f. this comment where there is a mention of multi-threaded (and thus shared) access despite dealing with exclusive references over unique ownership:

That seems like a different confusion, the comment after all explains that due to unique ownership there cannot be races. But yeah, the comment is confusing.

@RalfJung
Copy link
Member

This PR tries to move the sound &mut UnsafeCell -> &mut T projection that all the "downstream" constructions were already relying on

Well, all standard library users. We don't know what other clients do outside the standard library.
There is a slight chance that some of them rely on the fact that UnsafeCell, even when exposed directly to safe code, does not permit safe code to do anything. But I think we can discuss this on the tracking issue.

library/core/src/cell.rs Outdated Show resolved Hide resolved
@jyn514 jyn514 added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Sep 20, 2020
library/core/src/cell.rs Outdated Show resolved Hide resolved
library/core/src/cell.rs Outdated Show resolved Hide resolved
library/core/src/cell.rs Outdated Show resolved Hide resolved
library/core/src/cell.rs Outdated Show resolved Hide resolved
@RalfJung
Copy link
Member

Thanks. :) @bors r+ rollup

@bors
Copy link
Contributor

bors commented Sep 20, 2020

📌 Commit 853a9c4a4596892d6f69b9ec7f49177dfabbf1ef has been approved by RalfJung

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 20, 2020
@RalfJung
Copy link
Member

ah wait looks like you wanted to squash some thing first
@bors r-

@bors bors added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Sep 20, 2020
@danielhenrymantilla
Copy link
Contributor Author

Ok, I think we're getting there, @RalfJung 🙂 (thx for the review!).

Now that we have an example of "using UnsafeCell as un unchecked RefCell", would it make sense to also add an example of using UnsafeCell to implement some thread-safe-because-synchronized mutation abstraction? (The only problem would be to keep that example as small / minimal as possible 🤔)

@danielhenrymantilla
Copy link
Contributor Author

danielhenrymantilla commented Sep 20, 2020

I don't really know where to post the following, so here it goes:

Next steps / potential future PR(s)

Also, as a possible follow-up for this PR, I was thinking about something such as .assume_unique() and .assume_no_muts() (modulo bikeshed of course) since currently .get() serves a dual function of being able to yield &T and &mut T through its *mut T, thus leading to different patterns and thus different safety constraints, that may be non-obvious from the function calls. Indeed, compare:

unsafe {
    // SAFETY: within this scope there are no other references to `x`'s contents,
    // so ours is effectively unique.
    let p1_exclusive: &mut i32 = &mut *p1.get();
    *p1_exclusive += 27;
}

unsafe {
    // SAFETY: within this scope nobody expects to have exclusive access to `x`'s contents,
    // so we can have multiple shared accesses concurrently.
    let p2_shared: &i32 = &*p2.get();
    assert_eq!(*p2_shared, 42 + 27);
    let p1_shared: &i32 = &*p1.get();
    assert_eq!(*p1_shared, *p2_shared);
}

to:

unsafe {
    // SAFETY: within this scope there are no other references to `x`'s contents,
    // so ours is effectively unique.
    let p1_exclusive: &mut i32 = p1.assume_unique()
    *p1_exclusive += 27;
}

unsafe {
    // SAFETY: within this scope nobody expects to have exclusive access to `x`'s contents,
    // so we can have multiple shared accesses concurrently.
    let p2_shared: &i32 = p2.assume_no_muts();
    assert_eq!(*p2_shared, 42 + 27);
    let p1_shared: &i32 = p1.assume_no_muts();
    assert_eq!(*p1_shared, *p2_shared);
}

This would have the benefit of lifetime-bounding the obtained references, which is not a bad thing either.

Indeed, let's compare:

//! Misusage
let x = UnsafeCell::new(42);
let p1 = &x;
let at_ft_mut = unsafe { &mut *p1.get() };
let p2 = &mut x;
let at_ft_mut_2 = p2.get_mut();
mem::swap(at_ft_mut, at_ft_mut_2);

which compiles fine, to:

//! Misusage
let x = UnsafeCell::new(42);
let p1 = &x;
let at_ft_mut = unsafe { p1.assume_unique() };
let p2 = &mut x;
let at_ft_mut_2 = p2.get_mut();
mem::swap(at_ft_mut, at_ft_mut_2);

which fails with:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
  --> src/main.rs:9:14
   |
7  |     let p1 = &x;
   |              -- immutable borrow occurs here
8  |     let at_ft_mut = unsafe { p1.assume_unique() };
9  |     let p2 = &mut x;
   |              ^^^^^^ mutable borrow occurs here
10 |     let at_ft_mut_2 = p2.get_mut();
11 |     mem::swap(at_ft_mut, at_ft_mut_2);
   |               --------- immutable borrow later used here

@RalfJung
Copy link
Member

Now that we have an example of "using UnsafeCell as un unchecked RefCell", would it make sense to also add an example of using UnsafeCell to implement some thread-safe-because-synchronized mutation abstraction?

Honestly, that feels like a bit too much for a doc comment. That sounds more like a Nomicon chapter.^^

Also, as a possible follow-up for this PR, I was thinking about something such as .assume_unique() and .assume_no_muts() (modulo bikeshed of course) since currently .get() serves a dual function of being able to yield &T and &mut T through its *mut T, thus leading to different patterns and thus different safety constraints, that may be non-obvious from the function calls.

Yes, that sounds very useful. :) (for a separate PR though)

Update the tracking issue number

Updated the documentation for `UnsafeCell`

Address review comments

Address more review comments + minor changes
@danielhenrymantilla
Copy link
Contributor Author

Squashed 🙂 (I prefer not to force push while an interactive review is in process, since comments may get lost / track missing commits; now that that has been done, I can finally write the PR as two semantically meaningful commits: implement the method with the appropriate docs, and then simplify some unsafe { ...get() } calls to ...get_mut())

@RalfJung
Copy link
Member

I prefer not to force push while an interactive review is in process

That is much appreciated. :) It also makes it easier for me to see what changed from revision to revision.

@RalfJung
Copy link
Member

@bors r+ rollup

@bors
Copy link
Contributor

bors commented Sep 20, 2020

📌 Commit 5886c38 has been approved by RalfJung

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 20, 2020
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Sep 21, 2020
…mut, r=RalfJung

Add non-`unsafe` `.get_mut()` for `Unsafecell`

  - Tracking issue: rust-lang#76943

As discussed in: https://internals.rust-lang.org/t/add-non-unsafe-get-mut-for-unsafecell/12407

  - ### [Rendered documentation](https://modest-dubinsky-1f9f47.netlify.app/core/cell/struct.unsafecell)

This PR tries to move the sound `&mut UnsafeCell<T> -> &mut T` projection that all the "downstream" constructions were already relying on, up to the root abstraction, where it rightfully belongs, and officially blessing it.

  - this **helps reduce the amount of `unsafe` snippets out there** (_c.f._, the second commit of this PR: rust-lang@09503fd)

The fact that this getter is now expose for `UnsafeCell<T>` itself, will also help convey the idea that **`UnsafeCell` is not magical _w.r.t._ `&mut` accesses**, contrary to what some people incorrectly think.

  - Even the standard library itself at some point had such a confusion, _c.f._ this comment where there is a mention of multi-threaded (and thus _shared_) access despite dealing with exclusive references over unique ownership: https://github.com/rust-lang/rust/blob/59fb88d061544a035f3043b47594b34789204cee/library/core/src/cell.rs#L498-L499

r? @RalfJung
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Sep 21, 2020
…mut, r=RalfJung

Add non-`unsafe` `.get_mut()` for `Unsafecell`

  - Tracking issue: rust-lang#76943

As discussed in: https://internals.rust-lang.org/t/add-non-unsafe-get-mut-for-unsafecell/12407

  - ### [Rendered documentation](https://modest-dubinsky-1f9f47.netlify.app/core/cell/struct.unsafecell)

This PR tries to move the sound `&mut UnsafeCell<T> -> &mut T` projection that all the "downstream" constructions were already relying on, up to the root abstraction, where it rightfully belongs, and officially blessing it.

  - this **helps reduce the amount of `unsafe` snippets out there** (_c.f._, the second commit of this PR: rust-lang@09503fd)

The fact that this getter is now expose for `UnsafeCell<T>` itself, will also help convey the idea that **`UnsafeCell` is not magical _w.r.t._ `&mut` accesses**, contrary to what some people incorrectly think.

  - Even the standard library itself at some point had such a confusion, _c.f._ this comment where there is a mention of multi-threaded (and thus _shared_) access despite dealing with exclusive references over unique ownership: https://github.com/rust-lang/rust/blob/59fb88d061544a035f3043b47594b34789204cee/library/core/src/cell.rs#L498-L499

r? @RalfJung
RalfJung added a commit to RalfJung/rust that referenced this pull request Sep 21, 2020
…mut, r=RalfJung

Add non-`unsafe` `.get_mut()` for `Unsafecell`

  - Tracking issue: rust-lang#76943

As discussed in: https://internals.rust-lang.org/t/add-non-unsafe-get-mut-for-unsafecell/12407

  - ### [Rendered documentation](https://modest-dubinsky-1f9f47.netlify.app/core/cell/struct.unsafecell)

This PR tries to move the sound `&mut UnsafeCell<T> -> &mut T` projection that all the "downstream" constructions were already relying on, up to the root abstraction, where it rightfully belongs, and officially blessing it.

  - this **helps reduce the amount of `unsafe` snippets out there** (_c.f._, the second commit of this PR: rust-lang@09503fd)

The fact that this getter is now expose for `UnsafeCell<T>` itself, will also help convey the idea that **`UnsafeCell` is not magical _w.r.t._ `&mut` accesses**, contrary to what some people incorrectly think.

  - Even the standard library itself at some point had such a confusion, _c.f._ this comment where there is a mention of multi-threaded (and thus _shared_) access despite dealing with exclusive references over unique ownership: https://github.com/rust-lang/rust/blob/59fb88d061544a035f3043b47594b34789204cee/library/core/src/cell.rs#L498-L499

r? @RalfJung
bors added a commit to rust-lang-ci/rust that referenced this pull request Sep 21, 2020
Rollup of 13 pull requests

Successful merges:

 - rust-lang#76135 (Stabilize some Option methods as const)
 - rust-lang#76628 (Add sample defaults for config.toml )
 - rust-lang#76846 (Avoiding unnecesary allocations at rustc_errors)
 - rust-lang#76867 (Use intra-doc links in core/src/iter when possible)
 - rust-lang#76868 (Finish moving to intra doc links for std::sync)
 - rust-lang#76872 (Remove DeclareMethods)
 - rust-lang#76936 (Add non-`unsafe` `.get_mut()` for `Unsafecell`)
 - rust-lang#76958 (Replace manual as_nanos and as_secs_f64 reimplementations)
 - rust-lang#76959 (Replace write_fmt with write!)
 - rust-lang#76961 (Add test for issue rust-lang#34634)
 - rust-lang#76962 (Use const_cstr macro in consts.rs)
 - rust-lang#76963 (Remove unused static_assert macro)
 - rust-lang#77000 (update Miri)

Failed merges:

r? `@ghost`
@bors bors merged commit b670b86 into rust-lang:master Sep 21, 2020
@rustbot rustbot added this to the 1.48.0 milestone Sep 21, 2020
@danielhenrymantilla danielhenrymantilla deleted the unsafecell_get_mut branch September 22, 2020 15:58
@a1phyr
Copy link
Contributor

a1phyr commented Nov 4, 2020

Is there a reason why UnsafeCell::get_mut was not implemented like this ?

fn get_mut(&mut self) -> &mut T {
    &mut self.value
}

@danielhenrymantilla
Copy link
Contributor Author

Mostly an oversight 😅, I had been using an extension trait to implement this and thus tunnel-visioned on using the .get() API. Given that your implementation does not require unsafe, it will be better to use that one indeed: filed #78735.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants