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 map function to Ref and MutRef of RefCell #19220

Closed
wants to merge 2 commits into from

Conversation

tbu-
Copy link
Contributor

@tbu- tbu- commented Nov 22, 2014

This enables one to return references to things inside the primary RefCell
content, like this code:

use std::cell::RefCell;
use std::cell::Ref;

struct ContainsVector {
    vec: RefCell<Vec<u32>>,
}

impl ContainsVector {
    fn new() -> ContainsVector {
        ContainsVector { vec: RefCell::new(vec![0]) }
    }
    fn index<'a>(&'a self, i: uint) -> Ref<'a, &'a u32> {
        self.vec.borrow().map(|v| &v[i])
    }
    fn index_mut<'a>(&'a self, i: uint) -> RefMut<'a, &'a mut u32> {
        self.vec.borrow_mut().map(|v| &mut v[i])
    }
}

fn main() {
    let cv = ContainsVector::new();
    **cv.index_mut(0) = 1;
    cv.index(0);
}

This enables one to return references to things inside the primary `RefCell`
content, like this code:

```
use std::cell::RefCell;
use std::cell::Ref;

struct ContainsVector {
    vec: RefCell<Vec<u32>>,
}

impl ContainsVector {
    fn new() -> ContainsVector {
        ContainsVector { vec: RefCell::new(vec![0]) }
    }
    fn index<'a>(&'a self, i: uint) -> Ref<'a, &'a u32> {
        self.vec.borrow().map(|v| &v[i])
    }
    fn index_mut<'a>(&'a self, i: uint) -> RefMut<'a, &'a mut u32> {
        self.vec.borrow_mut().map(|v| &mut v[i])
    }
}

fn main() {
    let cv = ContainsVector::new();
    **cv.index_mut(0) = 1;
    cv.index(0);
}
```
@rust-highfive
Copy link
Collaborator

warning Warning warning

  • These commits modify unsafe code. Please review it carefully!

@tbu-
Copy link
Contributor Author

tbu- commented Nov 22, 2014

This pull request would change the semantics of RefCell, so I included the fixes for the standard library and rustc as an example.

Feedback would be appreciated! :)

@alexcrichton
Copy link
Member

The Cell and RefCell types are quite core abstractions in the standard library, so modifying them shouldn't be taken lightly. Could you please elaborate as to why you have modified the semantics, as well as precisely how the semantics have changed? It's difficult to see what has changed with this new map function as well as what this means for users of RefCell without deeply understanding the patch itself.

I would recommend a description in lines with our breaking changes policy or perhaps a mini-RFC-style description.

@tbu-
Copy link
Contributor Author

tbu- commented Nov 25, 2014

Motivation

Previously, you were unable to return a reference to something non-trivial inside of a RefCell. Consider you have this struct:

struct ContainsVector {
    vec: RefCell<Vec<u32>>,
}

Then you're currently unable to return a reference an element of the vector, despite this having somewhat clear semantics (it would keep the RefCell borrowed).

Proposed changes

This proposal changes the direct usage semantics of the Refs and RefMuts into a RefCell, although it doesn't change the actual representation: If you borrow a RefCell, you now get a Ref<&T> out of it, instead of a Ref<T>, which means that it needs a double-dereference (**) to access the actual value. This enables changing the interior of such a references via the map function, which takes a closure |T| -> U (both types bound by lifetime) and transforms a Ref<T> to a Ref<U>.

Advantages

This should be the most simple change in order to properly return references to inner members of RefCells, by reusing the existing Ref type and changing the usage semantics slightly. The changed usage semantics do not change the assembly output:

Before (approximation):

struct Ref<T> { val: &T }
let ref: Ref<T>;
ref = ref;
*ref = *ref.val;

After:

struct Ref<T> { val: &T }
let ref: Ref<T>;
ref = ref;
*ref = ref.val;
**ref = *ref.val;

(So the assembly output still just contains one dereference.)

Disadvantages

Usage sites that do not benefit from auto-derefencing need to be changed. This includes e. g.

*refcell.borrow_mut() = value;

to

**refcell.borrow_mut() = value;

@tbu-
Copy link
Contributor Author

tbu- commented Nov 25, 2014

@alexcrichton I believe the biggest argument against this change is that it changes a core type – however since we're still pre-1.0 it might be a bigger concern to get it right than to maintain backward-compatiblity.

@alexcrichton
Copy link
Member

I don't think that this is safe, I checked this out and this code compiles (when it shouldn't)

fn main() {                         
    let rc = RefCell::new(1u);

    let b: &uint = {                
        let a = rc.borrow();        
        *a                          
    };                              
    let mut c = rc.borrow_mut();    
    **c = 3;                        
    println!("{}", *b);             
}                                   

@tbu-
Copy link
Contributor Author

tbu- commented Nov 25, 2014

You're right, this is wrong. I'll look into this.

@SimonSapin
Copy link
Contributor

I’m making another attempt at this in #25747.

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

Successfully merging this pull request may close these issues.

4 participants