Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upAdd std::cell::Ref::map and friends #25747
Conversation
rust-highfive
assigned
alexcrichton
May 24, 2015
This comment has been minimized.
This comment has been minimized.
|
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @alexcrichton (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. The way Github handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see CONTRIBUTING.md for more information. |
This comment has been minimized.
This comment has been minimized.
|
https://github.com/rust-lang/rfcs#when-you-need-to-follow-this-process lists "Additions to Unresolved questions:
|
SimonSapin
force-pushed the
SimonSapin:map_ref
branch
4 times, most recently
from
62e8200
to
2b49fc6
May 24, 2015
This comment has been minimized.
This comment has been minimized.
|
I've added and experimented with this once. I scrapped the idea for my use case, because this makes it easy to return |
This comment has been minimized.
This comment has been minimized.
|
Wait, why don't you use a closure here? |
This comment has been minimized.
This comment has been minimized.
|
What borrowing error do you mean? This design of taking |
This comment has been minimized.
This comment has been minimized.
|
By borrowing error I mean a panic in |
This comment has been minimized.
This comment has been minimized.
|
Updated |
This comment has been minimized.
This comment has been minimized.
|
Sigh. I realized the PR as-is introduces memory unsafety: the closure can keep around the pointer it’s given longer than the lifetime of the #![feature(core)]
use std::cell::{RefCell, Ref, map_ref};
struct Foo {
destroyed: bool
}
impl Drop for Foo {
fn drop(&mut self) {
self.destroyed = true;
}
}
fn main() {
let a = RefCell::new(Box::new(Foo { destroyed: false }));
let mut b = None;
let _: Option<Ref<()>> = map_ref(a.borrow(), |s| {
b = Some(&**s);
None
});
println!("{}", b.unwrap().destroyed);
*a.borrow_mut() = Box::new(Foo { destroyed: false });
println!("{}", b.unwrap().destroyed); // Use after free
}I think making |
This comment has been minimized.
This comment has been minimized.
|
It looks like the closure idea was broken then. Sorry about that, my bad. |
This comment has been minimized.
This comment has been minimized.
|
cc #19220
I suspect not; couldn't one make |
This comment has been minimized.
This comment has been minimized.
|
Right. Here is an alternative idea. |
This comment has been minimized.
This comment has been minimized.
|
There's prior art that should definitely be considered at #19220. |
This comment has been minimized.
This comment has been minimized.
|
I believe this version is sound, it looks good. Why use an optional return value though? |
This comment has been minimized.
This comment has been minimized.
|
My use case it returning a new Example in Kuchiki: pub struct ElementData {
pub name: QualName,
pub attributes: RefCell<HashMap<QualName, String>>,
}
impl ElementData {
fn get_attribute(&self, name: Atom) -> Option<Ref<str>> {
// With `map_ref` as proposed:
map_ref(self.attributes.borrow(), |attrs| {
attrs.get(&QualifiedName { ns: ns!(""), local: name }).map(|s| &**s)
})
// With a simplified `map_ref`, two HashMap lookups:
let borrow = self.attributes.borrow();
let key = QualifiedName { ns: ns!(""), local: name };
if borrow.contains_key(&key) {
Some(map_ref(borrow, |attrs| &*attrs[&key]))
} else {
None
}
}
} |
SimonSapin
referenced this pull request
May 25, 2015
Closed
Add `map` function to `Ref` and `MutRef` of `RefCell` #19220
This comment has been minimized.
This comment has been minimized.
|
Unlike #19220, this doesn’t change the semantic of existing items (it just adds a new one), so I don’t think it has the same issues. The example program at #19220 (comment) correctly fails to build with a lifetime error (after updating it for language changes). |
This comment has been minimized.
This comment has been minimized.
|
Maybe my |
alexcrichton
added
the
T-libs
label
May 26, 2015
This comment has been minimized.
This comment has been minimized.
|
I, too, have wanted this kind of functionality from time to time before, and I'm totally fine seeing it prototyped! This is a minor enough API that I don't believe it will require an RFC, but I'll cc @rust-lang/libs to see if other have opinons as well :) I have the same question as @bluss as well in that from an API perspective, could you elaborate on why the closure returns
Naming-wise this may also wish to consider
We've been quite wary of adding methods on types that implement Apart from the |
This comment has been minimized.
This comment has been minimized.
|
Regarding pub enum NodeData {
Element(ElementData),
Text(String),
Comment(String),
Doctype(Doctype),
Document(DocumentData),
}
pub struct Node {
// ...
pub data: RefCell<NodeData>,
}
impl Node {
fn as_element(&self) -> Option<Ref<ElementData>> {
map_ref(self.data.borrow(), |data| match *data {
Element(ref element) => Some(element),
_ => None
})
}
}If pub fn map_ref<'b, T: ?Sized, U: ?Sized, F>(orig: Ref<'b, T>, f: F) -> Ref<'b, U>
where F: FnOnce(&T) -> &U… this would be more tricky to implement and require matching twice: impl Node {
fn as_element(&self) -> Option<Ref<ElementData>> {
let borrow = self.data.borrow();
match *borrow {
Element(_) => map_ref(borrow, |data| match *data {
Element(ref element) => element,
_ => unreachable!()
}),
_ => None
}
}
}I mentioned before that maybe I could instead return |
This comment has been minimized.
This comment has been minimized.
|
Added How can I run the doctests? |
This comment has been minimized.
This comment has been minimized.
|
I’ve found out (thanks eddyb!) that I can run the doctests with |
This comment has been minimized.
This comment has been minimized.
I'm worried about the compositionality of this API, however. For example if I have a I would personally rather see this as Also, it looks like associated functions can indeed be defined that aren't methods: struct A;
impl A {
fn foo(a: &A) {}
}
let a = A;
a.foo(); // error
A::foo(&a); // okPerhaps static |
This comment has been minimized.
This comment has been minimized.
|
Using static functions sounds great unless there is any plan to expand UFCS and make them resolve with method syntax. |
This comment has been minimized.
This comment has been minimized.
I agree that using a #![feature(core)]
use std::cell::*;
// So that I don’t have to re-bootrap...
fn simplified_map_ref<'b, T, U, F>(orig: Ref<'b, T>, f: F) -> Ref<'b, U>
where F: FnOnce(&T) -> &U {
map_ref(orig, move |v| Some(f(v))).unwrap()
}
fn main() {
let x: RefCell<Result<u32, ()>> = RefCell::new(Ok(5));
simplified_map_ref(x.borrow(), |r| &r.ok());
}a.rs:11:41: 11:47 error: borrowed value does not live long enough
a.rs:11 simplified_map_ref(x.borrow(), |r| &r.ok());
^~~~~~
note: in expansion of closure expansion
a.rs:11:36: 11:47 note: expansion site
a.rs:11:40: 11:47 note: reference must be valid for the anonymous lifetime #1 defined on the block at 11:39...
a.rs:11 simplified_map_ref(x.borrow(), |r| &r.ok());
^~~~~~~
a.rs:11:40: 11:47 note: ...but borrowed value is only valid for the block at 11:39
a.rs:11 simplified_map_ref(x.borrow(), |r| &r.ok());
^~~~~~~
error: aborting due to previous error |
This comment has been minimized.
This comment has been minimized.
|
I like the idea of using static methods on |
This comment has been minimized.
This comment has been minimized.
If we decide to do this I don’t see an obstacle to this: we can tell such functions apart from methods by them not using |
This comment has been minimized.
This comment has been minimized.
|
As discussed on IRC with @alexcrichton, I modified the PR to have both |
alexcrichton
reviewed
May 27, 2015
| #[unstable(feature = "core", reason = "recently added")] | ||
| #[inline] | ||
| pub fn map<U: ?Sized, F>(orig: RefMut<'b, T>, f: F) -> RefMut<'b, U> | ||
| where F: FnOnce(&mut T) -> &mut U { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
May 27, 2015
Member
Stylistically we tend to format functions like this as:
pub fn foo()
where ...
{
// ...
}
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Here is an attempt at generalizing impl<'b: 'c, 'c, T: ?Sized> Ref<'b, T> {
pub fn generalized_map<U: ?Sized, F, R>(orig: Ref<'b, T>, f: F) -> R
where F: FnOnce(&T, &FnOnce(&'c U) -> Ref<'c, U>) -> R {
f(orig._value, &move |new| Ref {
_value: new,
_borrow: orig._borrow,
})
}
}But using it doesn’t borrow-check: let x: RefCell<Result<u32, ()>> = RefCell::new(Ok(5));
let b: Option<Ref<u32>> = Ref::generalized_map(x.borrow(), |r, new_ref| match *r {
Ok(ref value) => Some(new_ref(value)),
Err(_) => None,
});
assert_eq!(*b.unwrap(), 5);
}I’m not sure if I’m doing something wrong or if this is not possible to express in current Rust. Regardless, a function that takes a closure that takes another closure is quite convoluted, not a great API. |
alexcrichton
reviewed
May 27, 2015
| /// This is an associated function that needs to be used as `Ref::clone(...)`. | ||
| /// A `Clone` implementation or a method would interfere with the widespread | ||
| /// use of `r.borrow().clone()` to clone the contents of a `RefCell`. | ||
| #[unstable(feature = "core", |
This comment has been minimized.
This comment has been minimized.
alexcrichton
May 27, 2015
Member
Could these features switch to something other than "core"? Something like cell_extras would be fine.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
This API looks good to me, and I also believe the implementation is sound, so I'm all good with this! I'd like to hear more opinions about |
This comment has been minimized.
This comment has been minimized.
|
+1 to the basic idea here.
This one is tricky. In the limit, with full inherent associated items, many "single type" modules could be flattened to just the type, and this is one step further in that direction. The main advantage is that you're usually importing the type already, so this saves you an additional import of the module/function. To be honest, I think it's inevitable that APIs will drift in this direction, and don't see much reason to fight it in (In the long run, this means that true free functions are predominantly going to be code that is not clearly associated with any particular type.)
I think the level of fanciness in the current PR is appropriate. |
This comment has been minimized.
This comment has been minimized.
|
Ok, thanks again for the PR @SimonSapin! r=me with a squash |
SimonSapin
changed the title
Add std::cell::map_ref
Add std::cell::Ref::map and friends
May 28, 2015
SimonSapin
force-pushed the
SimonSapin:map_ref
branch
from
04f106f
to
64e72b0
May 28, 2015
SimonSapin
added a commit
to SimonSapin/rust
that referenced
this pull request
May 28, 2015
This comment has been minimized.
This comment has been minimized.
|
Squashed into two commits. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
May 28, 2015
This comment has been minimized.
This comment has been minimized.
|
|
SimonSapin
force-pushed the
SimonSapin:map_ref
branch
from
64e72b0
to
d0afa6e
May 29, 2015
This comment has been minimized.
This comment has been minimized.
|
Oh noes. Should be fixed, with this diff before squashing: diff --git a/src/libcoretest/lib.rs b/src/libcoretest/lib.rs
index 78c7215..3d14b3f 100644
--- a/src/libcoretest/lib.rs
+++ b/src/libcoretest/lib.rs
@@ -24,6 +24,7 @@
#![feature(step_by)]
#![feature(slice_patterns)]
#![feature(float_from_str_radix)]
+#![feature(cell_extras)]
extern crate core;
extern crate test; |
SimonSapin commentedMay 24, 2015
For slightly complex data structures like
rustc_serialize::json::Json, it is often convenient to have helper methods likeJson::as_string(&self) -> Option<&str>that return a borrow of some component of&self.However, when
RefCells are involved, keeping aRefaround is required to hold a borrow to the insides of aRefCell. ButRefso far only references the entirety of the contents of aRefCell, not a component. But there is no reason it couldn’t:Refinternally contains just a data reference and a borrow count reference. The two can be dissociated.This adds a
map_reffunction that creates a newReffor some other data, but borrowing the sameRefCellas an existingRef.Example:
r? @alexcrichton