On Tue, Jan 22, 2013 at 6:23 PM, Graydon Hoare <[email protected]> wrote:
> On 22/01/2013 6:55 AM, Dean Thompson wrote: > > > I'm looking at some code that Niko Matsakis updated in > > https://github.com/stevej/rustled/commits/master/red_black_tree.rs > > > > pure fn each(&self, f: fn(&(&self/K, &self/V)) -> bool) { > > match *self { > > Leaf => (), > > Tree(_, ref left, ref key, ref maybe_value, ref right) => { > > let left: &self/@RBMap<K,V> = left; > > let key: &self/K = key; > > let maybe_value: &self/Option<V> = maybe_value; > > let right: &self/@RBMap<K,V> = right; > > left.each(f); > > match *maybe_value { > > Some(ref value) => { > > let value: &self/V = value; > > f(&(key, value)); > > } > > None => () > > }; > > right.each(f); > > } > > } > > } > > > > I understand this code reasonably well. I greatly value the attention > > to safety in Rust, and I appreciate the value of pointer lifetimes in > > maintaining that safety. > > > > My gut reaction, though, is that this code is almost as intimidating > > as Haskell. Even more worrisome to me, I think most mainstream > > programmers would find the *explanation* of this code intimidating. > > I agree that the cognitive load on this code sample is high. This is the > main risk we took (aside from "potential unsoundness", which I didn't > really think to be a big risk, judging from Niko's comfort with the > semantics) when adopting first class region pointers: that the resulting > types would be too complex to understand, and/or require too much > chatter when writing out in full. > > To my eyes the matter is not yet entirely clear. It's complex but it's > not quite "impossibly complex"; if you made all the '&self/' symbols > into just '&' it would be, I think, not so bad. Compare if you like to > the associated bits of code from libc++ required to implement > roughly-equivalent "iterate through the treemap" sort of functionality: > > And with this clue, I think I can jump into the discussion because I know *nothing* about the semantics you are discussing. This is my wild guess at what is happening in this function, and what is not obvious, line by line: > pure fn each(&self, f: fn(&(&self/K, &self/V)) -> bool) { There is some difference between & and 'ref', but they are both some sort of reference. 'fn' is a callback function that takes a tuple with the key and value. The &self/K syntax is impossible to understand, but given your clue that I could just assume it doesn't exist, I will guess that &self/K means that the lifetime of K is somehow bound to &self, so that the callback function must copy K and V if they should be retained. I am pretty familiar with the concept of alias analysis in a compiler. It is not obvious why there is a '&self/' instead of 'self/' though. > match *self { I'm not sure why we need *self here, but it looks like dereferencing so we're matching on the structure of the tree. > Leaf => (), > Tree(_, ref left, ref key, ref maybe_value, ref right) => { As I said earlier, why this is 'ref' and not '&' is not clear, but it is "obvious" that they are both some kind of reference. > let left: &self/@RBMap<K,V> = left; > let key: &self/K = key; > let maybe_value: &self/Option<V> = maybe_value; > let right: &self/@RBMap<K,V> = right; The above is sort of straight forward, but I am a little disappointed that the &self/ stuff isn't inferred, given that we have f(&(key, value)) below. I mean, if my intuition on &self/ is correct, then in Haskell at least, the '&self/' part of the 'key' and 'value' would be inferred by the compiler because of the restriction in the type of 'fn'. > left.each(f); This tells me that the first parameter to the function is special. I am not sure whether each(left, f) would be valid or not. > match *maybe_value { Here again, it seems like using the '*' is a little unnecessary syntax. When would we want to match on a reference which can only be a reference? > Some(ref value) => { > let value: &self/V = value; > f(&(key, value)); Given the above, I ask myself why this can't be written like this: Some(ref &self/V value) => { f (&(key, value)); } or something like that? > } > None => () I ask myself whether this case is needed. The return value of the 'match *maybe_value' is not used, since this is imperative code. Probably this is here because the compiler enforces full case analysis and the runtime will crash on an unmatched item. Maybe Rust is missing something that doesn't enforce full case analysis, I think: match_some *maybe_value { ... } > }; > right.each(f); > } > } > } All in all, my feeling after trying to understand the code is that it is too verbose, and that especially the '&self/' thing should be in some sort of type inference part of the compiler. Alexander > > _LIBCPP_INLINE_VISIBILITY > __tree_iterator& operator++() { > __ptr_ = static_cast<__node_pointer( > __tree_next( > static_cast<__node_base_pointer>(__ptr_))); > return *this; > } > > template <class _NodePtr> > _NodePtr > __tree_next(_NodePtr __x) _NOEXCEPT > { > if (__x->__right_ != nullptr) > return __tree_min(__x->__right_); > while (!__tree_is_left_child(__x)) > __x = __x->__parent_; > return __x->__parent_; > } > > template <class _NodePtr> > inline _LIBCPP_INLINE_VISIBILITY > bool > __tree_is_left_child(_NodePtr __x) _NOEXCEPT > { > return __x == __x->__parent_->__left_; > } > > template <class _NodePtr> > inline _LIBCPP_INLINE_VISIBILITY > _NodePtr > __tree_min(_NodePtr __x) _NOEXCEPT > { > while (__x->__left_ != nullptr) > __x = __x->__left_; > return __x; > } > > And keep in mind that there is no memory-safety in that code: if I > invalidate a C++ map while iterating, I just get a wild pointer > dereference and crash. If I rewrote it in terms of shared_ptr<> it'd be > even chattier. > > > Who is our target audience for Rust? Graydon has said it is > > "frustrated C++ developers", but how sophisticated and how "brave" > > are we thinking they will be? > > The target audience is frustrated C++ developers, same as always. If > they balk at the syntax for lifetime-bounds on borrowed pointers, then > yes, we've blown the cognitive budget, and have failed. > > It is not clear to me yet that that's true. But it's a risk. One we're > all aware of and worried about. > > > How intimidating do we think Rust is today? Am I just overreacting > > to unfamiliarity? > > I don't know. It's a very hard thing to measure. I know of lots of > languages that have failed for this reason. It's a major hazard. > > > How can we calibrate our "intimidation factor" before language > > decisions start getting harder to change? > > If you search our mailing list, IRC logs or meeting minutes for > "cognitive budget", "cognitive load" or "cognitive burden" you will see > we have always been keenly aware of this risk and treat it as a primary > constraint when doing design work. It's a leading reason why many > features have been removed, simplified, minimized or excluded from > consideration. > > > Do we want (and is it feasible) to define a simpler subset of the > > language that beginners are encouraged to stick to and that most > > libraries don't force clients away from? > > Personal opinion: no. That just makes the issue even more confusing. The > way to approach this is head-on, by looking at the things that cause the > most confusion and trying to make them cause less. > > Thanks for bringing this up. I'm interested to hear others' opinions on > whether we're past a reasonable limit of comprehensibility. It's a hard > thing to hear, but better to hear now than later, if true. > > -Graydon > > _______________________________________________ > 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
