Re: [rust-dev] Virtual fn is a bad idea
Recently I saw a video of person from scala team who regretted some 'pragmatic' choices in long run so I might be still under it's impression regarding pragmatic choices in a language. Fortunately (or unfortunately) I'm not in charge of Rust design. Also last question - why not use: struct Element { //... element specific elements } struct Attribute { //... attribute specific elements } enum NodeChild { NodeAttribute(Attribute), NodeElement(Element) } struct Node'r { parent: 'r Node, first_child: 'r Node, last_child: 'r Node, next_sibling: 'r Node, last_sibling: 'r Node, node: NodeChild } For static types: struct Element'r { elem: 'r Node } pub fn downcast_to_element'r(node: 'r Node) - OptionElement'r { match (node.node) { NodeElement(_) - Some(Element {elem: node}), _ - None } } + impl with potentially partial methods? Is the 'all nodes consuming same amount of memory' too much memory? 1. One word pointers - check 2. Access to fields - check 3. Downcasting and upcasting - check 4. Inheritance with prefix property - check (sort of) 5. No need for behind-the-back optimization - check -. static dispatch (check) at the cost of match instead of jump with all its pros and cons Best regards On Tue, 2014-03-11 at 19:47 -0700, Patrick Walton wrote: I thought about tricks like this, but (a) a sufficiently smart compiler should *not* be doing this automatically, as it makes certain common operations like fetching the pointer more expensive as well as violating the principle of least surprise when it comes to machine representation, and (b) does this sort of hack really result in a cleaner design than having some simple language extensions? Mind you, I'm all about ways to simplify Rust, but sometimes the simplest solution is to build stuff into the language. On March 11, 2014 7:39:30 PM PDT, Maciej Piechotka uzytkown...@gmail.com wrote: On Tue, 2014-03-11 at 14:18 -0700, Patrick Walton wrote: On 3/11/14 2:15 PM, Maciej Piechotka wrote: Could you elaborate on DOM? I saw it referred a few times but I haven't seen any details. I wrote simple bindings to libxml2 dom (https://github.com/uzytkownik/xml-rs - warning - I wrote it while I was learning ruby) and I don't think there was a problem of OO - main problem was mapping libxml memory management and rust's one [I gave up with namespaces but with native rust dom implementation it would be possible to solve in nicer way]. Of course - I might've been at too early stage. You need: 1. One-word pointers to each DOM node, not two. Every DOM node has 5 pointers inside (parent, first child, last child, next sibling, previous sibling). Using trait objects would 10 words, not 5 words, and would constitute a large memory regression over current browser engines. 2. Access to fields common to every instance of a trait without virtual dispatch. Otherwise the browser will be at a significant performance disadvantage relative to other engines. 3. Downcasting and upcasting. 4. Inheritance with the prefix property, to allow for (2). If anyone has alternative proposals that handle these constraints that are more orthogonal and are pleasant to use, then I'm happy to hear them. I'm just saying that dismissing the feature out of hand is not productive. Patrick Ok. I see where my misunderstanding was - I was thinking about DOM implementation in Ruby for Ruby while you (Mozilla) were talking about implementation in Ruby for JavaScript. Please feel free to ignore next paragraph as I haven't given it much though but my guess would be that it would be possible to avoid the penalty by enum + alignment + smart compiler. As data have 6 words + in your scheme (5 described + vtable) the 4 bit alignment (assuming 32+ bit platform) should not cause much memory waste. This allows for using the 'wasted' bits for other purposes (the trick is used in, for example, lock-free structures) - for example: enum ElementChild'r { ElementChildElement('r Element), ElementChildComment('r Comment), } Could be represented as pointer
Re: [rust-dev] Virtual fn is a bad idea
On Tue, 2014-03-11 at 19:09 +, Bill Myers wrote: I see a proposal to add virtual struct and virtual fn in the workweek meeting notes, which appears to add an exact copy of Java's OO system to Rust. I think however that this should be carefully considered, and preferably not added at all (or failing that, feature gated and discouraged). The core problem of virtual functions (shared by Java's classes, etc.) is that rather than exposing a single public API, they expose two: the API formed by public functions, and the API formed by virtual functions to be overridden by subclasses, and the second API is exposed in an inflexible and unclean way. A much better way of allowing to override part of a struct's behavior is by defining a trait with the overridable functionality, and allowing to pass in an implementation of the trait to the base class, while also providing a default implementation if desired. Another way is to have the subclass implement all the traits that the base class implements, include a field of the base class type, and then direct all non-overridden functionality to the base class (here syntax sugar can be easily added to eliminate the boilerplate, by automatically implementing all non-implemented trait functions by calling the same function on the base class field). These approaches can be combined, as the first approach allows to change the inside behavior of the base class, while the second one allows to put extra behavior around the base class code. The fact that OO using virtual functions (as opposed to traits) is a bad design is one of the crucial steps forward of the design of languages like Go and current Rust compared to earlier OO languages, and Rust should not go backwards on this. Here is a list of issues with virtual functions: 1. Incentive for bad documentation Usually there is no documentation for how virtual functions are supposed to be overridden, and it as awkward to add it since it needs to be mixed with the documentation on how to use the struct 2. Mishmashing multiple unrelated APIs With traits, you could pass in multiple objects to implement separate sets of overridable functionality; with virtual structs you need to mishmash all those interfaces into a single set of virtual functions, all sharing data even when not appropriate. 3. No encapsulation Private data for virtual function implementations is accessible to all other functions in the struct. This means for instance that if you have a virtual function called compute_foo() that is implemented by default by reading a foo field in the base class, then all other parts of the base class can access foo too. If anything else accesses mistakenly foo directly, which it can, then overriding compute_foo() will not work as expected. If compute_foo() were provided by an external trait implementation, then foo would be private and inaccessible, eliminating the problem. 4. Data for overridden implementations left there in a zombie state. In the above example, if you override compute_foo(), the foo variable in the base class will no longer be used, yet it will still be present in the type and allocated in memory. 5. Inability to statically dispatch With a trait implementation, you can pass the concrete type as a generic parameter, allowing static dispatch. If you instead call an overridable virtual function, then you can't dispatch that statically at all (unless you add cumbersome syntax for that). 6. Adds a ton of unnecessary complexity to the language 7. Harder interoperability. For example I've encountered at least 2 Float interfaces for Java from 2 different libraries (both trying to abstract over Float) which I need to use in one of my projects (long story) - either one needed to have an adapter or there was a need to convert float to float. In total there were 3 32-bit floats representation in single function counting also the float. With traits they would just add implementation to Java's float. Best regards signature.asc Description: This is a digitally signed message part ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] Virtual fn is a bad idea
On Tue, 2014-03-11 at 14:37 -0500, Evan G wrote: ... Why didn't they just extend Number? (Or, at worst, that was a bad design decision on Oracle's part, by choosing to make the Float class final) Either way, I don't see how that's a fault of the language. I don't remember - maybe they had, but it wouldn't solved the problem. And Float is final - and even if it hadn't been it wouldn't solve the problem at all. Assume that Float is not final and they did extended Number. - Platform P provides interface PI and object PO (say Number and Float) - Component A provided and required interface AI and object AO extending PO and implementing AI and PI - Component B provided and required interface BI and object AO extending PO and implementing BI and PI Now you cannot pass object AO to component B as it requires BI. You need either: - Provide adapter from AI to BI (or other way round) which implements AI, BI and PI - Each time convert from AO to BO when you transfer between interfaces In proposed interface there would be only second option due to single inheritance. On the other hand with traits: - Platform P provides interface PI and object PO - Component A provides and requires interface AI and implements AI for PO (there is no need for adding AI as PO is 'open' for trait implementation) - Component B provides and requires interface BI and implements BI for PO (there is no need for adding BI as PO is 'open' for trait implementation) The user needs to do: - Nothing. Everything works out of the box And before you ask - component A and B were 2 different libraries for which the Oracle interfaces were insufficient. Best regards On Tue, Mar 11, 2014 at 2:35 PM, Maciej Piechotka uzytkown...@gmail.com wrote: On Tue, 2014-03-11 at 19:09 +, Bill Myers wrote: I see a proposal to add virtual struct and virtual fn in the workweek meeting notes, which appears to add an exact copy of Java's OO system to Rust. I think however that this should be carefully considered, and preferably not added at all (or failing that, feature gated and discouraged). The core problem of virtual functions (shared by Java's classes, etc.) is that rather than exposing a single public API, they expose two: the API formed by public functions, and the API formed by virtual functions to be overridden by subclasses, and the second API is exposed in an inflexible and unclean way. A much better way of allowing to override part of a struct's behavior is by defining a trait with the overridable functionality, and allowing to pass in an implementation of the trait to the base class, while also providing a default implementation if desired. Another way is to have the subclass implement all the traits that the base class implements, include a field of the base class type, and then direct all non-overridden functionality to the base class (here syntax sugar can be easily added to eliminate the boilerplate, by automatically implementing all non-implemented trait functions by calling the same function on the base class field). These approaches can be combined, as the first approach allows to change the inside behavior of the base class, while the second one allows to put extra behavior around the base class code. The fact that OO using virtual functions (as opposed to traits) is a bad design is one of the crucial steps forward of the design of languages like Go and current Rust compared to earlier OO languages, and Rust should not go backwards on this. Here is a list of issues with virtual functions: 1. Incentive for bad documentation Usually there is no documentation for how virtual functions are supposed to be overridden, and it as awkward to add it since it needs to be mixed with the documentation on how to use the struct 2. Mishmashing multiple unrelated APIs With traits, you could pass in multiple objects to implement separate sets of overridable functionality; with virtual structs you need to mishmash all those interfaces into a single set of virtual functions, all sharing data even when not appropriate. 3. No encapsulation Private data for virtual function implementations is accessible to all other functions in the struct. This means for instance that if you have a virtual function called compute_foo() that is implemented by default by reading a foo field in the base class, then all other parts of the base
Re: [rust-dev] Virtual fn is a bad idea
See for example http://jscience.org/api/org/jscience/mathematics/structure/Field.html and http://jscience.org/api/org/jscience/mathematics/number/Float64.html. Now you cannot just use Double as to use http://jscience.org/api/org/jscience/mathematics/function/Polynomial.html you need to have a type that implements Ring. Then you needed to perform a few operations on polynomial and feed it to different library which had it own interfaces for float. You cannot just add static method as you're not an user of it - the other component is so using interface to abstract numeric operations, so function is not a viable workaround (BTW - I don't remember if jscience is one of libraries which I've used - they just appeared as one of first in Google). And sure, in ideal world all packages would have one interface but it wasn't something I controlled or anyone remotely connected with project. And I wasn't somehow inclined to rewrite 2 libraries. Another example would be interface which could be implemented in terms of another. So interface A is subset of B. You could make A a subinterface of B but it isn't and it is not something you control (for example first project don't want to depend on second, or does not know of its existence, or the work is in progress but it hasn't been done yet). If you happen to have control over whole system you're in much easier situation as you can make a single sane hierarchy. If you don't and you try to pull a few external libraries to work together as a small component of a larger project it's much more of a problem. Best regards On Tue, 2014-03-11 at 15:00 -0500, Evan G wrote: I still don't a hundred percent understand... what interface could there be that doesn't require the object to store the state necessary to implement it? I mean, anything else is really just a function, instead of 60.days_after(date) use days_after(60, date). On Tue, Mar 11, 2014 at 2:51 PM, Maciej Piechotka uzytkown...@gmail.com wrote: On Tue, 2014-03-11 at 14:37 -0500, Evan G wrote: ... Why didn't they just extend Number? (Or, at worst, that was a bad design decision on Oracle's part, by choosing to make the Float class final) Either way, I don't see how that's a fault of the language. I don't remember - maybe they had, but it wouldn't solved the problem. And Float is final - and even if it hadn't been it wouldn't solve the problem at all. Assume that Float is not final and they did extended Number. - Platform P provides interface PI and object PO (say Number and Float) - Component A provided and required interface AI and object AO extending PO and implementing AI and PI - Component B provided and required interface BI and object AO extending PO and implementing BI and PI Now you cannot pass object AO to component B as it requires BI. You need either: - Provide adapter from AI to BI (or other way round) which implements AI, BI and PI - Each time convert from AO to BO when you transfer between interfaces In proposed interface there would be only second option due to single inheritance. On the other hand with traits: - Platform P provides interface PI and object PO - Component A provides and requires interface AI and implements AI for PO (there is no need for adding AI as PO is 'open' for trait implementation) - Component B provides and requires interface BI and implements BI for PO (there is no need for adding BI as PO is 'open' for trait implementation) The user needs to do: - Nothing. Everything works out of the box And before you ask - component A and B were 2 different libraries for which the Oracle interfaces were insufficient. Best regards On Tue, Mar 11, 2014 at 2:35 PM, Maciej Piechotka uzytkown...@gmail.com wrote: On Tue, 2014-03-11 at 19:09 +, Bill Myers wrote: I see a proposal to add virtual struct and virtual fn in the workweek meeting notes, which appears to add an exact copy of Java's OO system to Rust. I think however that this should be carefully considered, and preferably not added at all (or failing that, feature gated and discouraged). The core problem of virtual functions (shared by Java's classes, etc.) is that rather than exposing
Re: [rust-dev] Virtual fn is a bad idea
On Tue, 2014-03-11 at 13:44 -0700, Patrick Walton wrote: On 3/11/14 1:42 PM, Daniel Micay wrote: Existing object systems like COM, DOM and gobject are worth looking at, but Rust shouldn't bend over backwards to support them. They're legacy technologies and while interacting with them is important, I don't think it should result in any extra complexity being added to Rust. We have to support the technologies that are in use in a pleasant way, or else Rust will not be practical. Regardless of your feelings about existing OO systems, Rust has to support them well. So far nobody in this thread has demonstrated an understanding of the constraints here. Traits are simply not sufficient to model the DOM, for example. Patrick Could you elaborate on DOM? I saw it referred a few times but I haven't seen any details. I wrote simple bindings to libxml2 dom (https://github.com/uzytkownik/xml-rs - warning - I wrote it while I was learning ruby) and I don't think there was a problem of OO - main problem was mapping libxml memory management and rust's one [I gave up with namespaces but with native rust dom implementation it would be possible to solve in nicer way]. Of course - I might've been at too early stage. Regarding existing OO systems - Haskell interops with few of them (like gtk+ for example) using typeclasses without problems I know of. Possible next stage would be modelling the same hierarchy but since most systems use multiple inheritance in one form or another it would not help much. Best regards signature.asc Description: This is a digitally signed message part ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] Virtual fn is a bad idea
On Tue, 2014-03-11 at 14:18 -0700, Patrick Walton wrote: On 3/11/14 2:15 PM, Maciej Piechotka wrote: Could you elaborate on DOM? I saw it referred a few times but I haven't seen any details. I wrote simple bindings to libxml2 dom (https://github.com/uzytkownik/xml-rs - warning - I wrote it while I was learning ruby) and I don't think there was a problem of OO - main problem was mapping libxml memory management and rust's one [I gave up with namespaces but with native rust dom implementation it would be possible to solve in nicer way]. Of course - I might've been at too early stage. You need: 1. One-word pointers to each DOM node, not two. Every DOM node has 5 pointers inside (parent, first child, last child, next sibling, previous sibling). Using trait objects would 10 words, not 5 words, and would constitute a large memory regression over current browser engines. 2. Access to fields common to every instance of a trait without virtual dispatch. Otherwise the browser will be at a significant performance disadvantage relative to other engines. 3. Downcasting and upcasting. 4. Inheritance with the prefix property, to allow for (2). If anyone has alternative proposals that handle these constraints that are more orthogonal and are pleasant to use, then I'm happy to hear them. I'm just saying that dismissing the feature out of hand is not productive. Patrick Ok. I see where my misunderstanding was - I was thinking about DOM implementation in Ruby for Ruby while you (Mozilla) were talking about implementation in Ruby for JavaScript. Please feel free to ignore next paragraph as I haven't given it much though but my guess would be that it would be possible to avoid the penalty by enum + alignment + smart compiler. As data have 6 words + in your scheme (5 described + vtable) the 4 bit alignment (assuming 32+ bit platform) should not cause much memory waste. This allows for using the 'wasted' bits for other purposes (the trick is used in, for example, lock-free structures) - for example: enum ElementChild'r { ElementChildElement('r Element), ElementChildComment('r Comment), } Could be represented as pointer with last bit specifying if it's element or comment - similar to OptionT optimization. The lookup should behave much nicer with respect to branch prediction (plus) but getting pointer is more complicated (minus) and possibly longer code instead of jump (minus). If data alignes the compiler should be able to optimize the jumps if it notices that all jumps lead to the same pointer arithmetic. I'm not sure about handles from JS but I don't think there is more then 16 choices for types of parent/child/sibling for any node so it should be achivable - on language side it would just be enum + pointer (+ specification of alignment as attribute?). That said a) I have done no measurements/benchmarks so my intuition is likely to be wrong b) should in above paragraph means 'it looks that it could work after 5s thought' and c) I'm not a Rust designer and I don't pay for nor contribute so I don't expect that I should have anything resembling last word. Best regards signature.asc Description: This is a digitally signed message part ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] About RFC: A 30 minute introduction to Rust
On Tue, 2014-03-04 at 23:54 -0300, Fernando Pelliccioni wrote: We still have the problem of dangling references. Any decent compiler can deal with this problem, and according to the Standard the implementations are encouraged to issue a warning in such a case. I don't know implementations that don't do it. GCC: In function 'int dangling()': warning: reference to local variable 'i' returned [-Wreturn-local-addr] int i = 1234; ^ Clang warning: reference to stack memory associated with local variable 'i' returned [-Wreturn-stack-address] return i; ^ The same happens with MSVC. Do you think that the Warning is not much? Well, you can use a compiler option -Werror Or in this case -Werror-return-stack-address ...and... voilĂ , the problem is over, we now have errors instead of warnings. What is the advantage of Rust now? I think it's insignificant. I usually find myself defending C++ in most discussion but it's not ideal (although from what I heard about article it is much less horrible then presented). #include iostream class Test { public: Test(int i) : i(i) {} int get_i() const { i++ return i; } private: int i; }; Test foo(int i) { return Test(i); } int main() { Test a = foo(1); Test b = foo(2); std::cout a.get_i() b.get_i() std::endl; return 0; } No warnings on -Wall -Wextra -O1. Of course -O1 and -O0 have different results as we are happy to write somewhere on stack and in first case the valgrind does not report any errors. Add more code, foo begin a bit longer (or getting itself a reference) multiple files, Cheshire Cats... Once you get any structure more complicated then tree you'll need to use pointers. You can use std::shared_pointer as long as your structure is DAG - once it isn't you are on your own. 'Rust' have similar problem - but it has compile error instead of potentially a warning (potentially as C++ needs to have 'sufficient' knowledge). (Not mentioning the reference invalidation mentioned earlier) Best regards signature.asc Description: This is a digitally signed message part ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev