The solution linked to in "Go has a suitable answer" isn't relevant, because it doesn't support virtual methods. You can do the same thing described there in Rust by having a base struct, embedding the base struct in any child structs (composition over inheritance), and implementing Deref/DerefMut on the child if you like.
Go has the exact same set of features Rust has here, no more: struct composition or interfaces/traits.
Regarding graphs: The best solution is option 3, using a crate on crates.io (petgraph is the most popular, but rust-forest is fine too). The preferred solution to having multiple types of nodes in the same tree is to use an enum or a trait. (Servo uses a trait for its flow tree, with downcast methods like as_block() if needed.)
I'm puzzled by the contention that the fact that all nodes have to be the same type is a problem with Rust. That's always true in any language. Even in C, you'd have to have your next/previous pointers have some type, or else you couldn't traverse the list!
I don't know anything about Rust itself but I find it amusing that the author thought that six months should have been more than enough time for the community to agree on and implement a nontrivial change in how the language works.
Yeah. As a non-Rust developer I was reading the post with some interest and a little sympathy until I saw his timeline.
"Right, so they've been working on this potential change since 2016, and...wait. 2016?!"
If he'd said 2014 or earlier, I'd consider him to have a point. 2015 is asking for a bit too much, but I can still see his frustration; I'd call that a wash. But 2016? It destroyed his credibility with me, because it's clear he has no idea what he's talking about when it comes to developing a programming language.
I know that sounds harsh but...man. He's leaving Rust because they're taking more than six months to implement a massive change to a core language mechanic? Have fun working with absolutely any other language ever.
Exactly. If you read that thread you can see there's careful and ongoing technical discussion of the details necessary to implement the feature.
It hasn't languished, it's not just a bunch of +1s met with developer silence. What precisely is the author expecting the Rust team to do better on here?
While I do agree that six month is not quite long, there's another issue: in the RFC PR, most of discussions took place before Mar 27, which is 10 days after when the RFC was submitted. Then there were a few comments on on Apr 28, and nothing really happened until Aug 4, when someone mentioned they were interested in the RFC, and there only has been minor discussions after that compared to the situation before Mar 27.
I might be wrong but judging from this, I felt like we might never reach a conclusion on this topic even in a considerably long enough time, since it did seem to me that the core team is not interested in this feature.
From this point of view, I don't think it matters too much when we debate if six month is long enough, even if we waited longer, I'm not sure if the situation is gonna change when interest in this feature stays low.
Rust seems to be a poor match for the way the author wishes to solve this particular problem. That doesn't mean that their design is wrong, or that Rust is wrong, but it does mean that mixing the two would require rethinking parts of the design.
> So a trait can define abstract functions, but can't access any underlying fields.
In Rust, a trait is a purely abstract interface to a type. It explains how you can use that type, but it knows nothing about the implementation.
I'm not quite sure what the design goal was with the Widget type, but the closest solution is to replace the member variable in the abstract type with some kind of accessor (and to replace the magic negative font sizes with an Option):
trait Widget {
// ADDED: Access whatever underlying font
// size you have stored in this type.
fn desired_font_size(&self) -> Option<i32>;
// ADDED: Access the theme of this object.
fn theme(&self) -> &Theme;
// High-level interface to get font size.
// (With a default implementation in terms of
// other functions on this trait.)
fn font_size(&self) -> i32 {
if let Some(sz) = self.desired_font_size() {
sz
} else {
self.theme().get_standard_font_size()
}
}
}
I'm not sure this is how I'd personally design this, but it should compile.
> What IS the idiomatic Rust way to do a cyclical directed graph?
Unfortunately, the correct idiomatic way is that you try very hard to avoid doing so. Rust is all about clear ownership, and it doesn't like circular references.
The usual solution is to put the cyclic graph code into a library, and to use pointers and about 20 lines of unsafe code. (EDIT: See below for a better solution.) It's not much different from the C++ solution.
The Servo team really wants GCed, safe pointer support in Rust, and design work is well underway. But it's going to take a while to stabilize.
GUI libraries are an interesting special case: They involve huge class hierarchies, lots of implementation inheritance, circular references, and tricky ownership. Most older OO languages were optimized for this case. Newer languages tend to favor flatter type hierarchies and far less implementation inheritance. Which means that traditional object-oriented GUI designs may be awkward.
> The usual solution is to put the cyclic graph code into a library, and to use pointers and about 20 lines of unsafe code. It's not much different from the C++ solution.
Well, petgraph uses vectors and indices to avoid unsafe code, and it's the most popular graph library on crates.io.
Really, the answer here is "use a graph crate on crates.io". If you don't know which one to use, the best answer is probably petgraph.
> The Servo team really wants GCed, safe pointer support in Rust, and design work is well underway. But it's going to take a while to stabilize.
We aren't going to use GC'd pointers for any of the trees except the DOM. GC is way overkill for this use case. The reason why we want GC support is not to make tree data structures: it's for integration with JavaScript.
> GUI libraries are an interesting special case: They involve huge class hierarchies, lots of implementation inheritance, circular references, and tricky ownership.
I doubt they have to be. There are other ways to do GUI, see for instance how Light Table uses a database-like Entity Component System. https://www.youtube.com/watch?v=V1Eu9vZaDYw
> > What IS the idiomatic Rust way to do a cyclical directed graph?
> Unfortunately, the correct idiomatic way is that you try very hard to avoid doing so. Rust is all about clear ownership, and it doesn't like circular references.
> The usual solution is to put the cyclic graph code into a library, and to use pointers and about 20 lines of unsafe code.
The point about C++ is important. Cyclic graphs are hard in any non-GC'ed language because it breaks the ownership story.
Given that Rust was designed by a group known for writing web browsers, how does that work out in practice, given that the DOM is a deep bidirectional graph?
UIs work routinely by the organizational principle the author is attempting. Nested groupings of related functions that often interact in small groups.
And the thing is, a pointer to your parent doesn't semantically mean an ownership relationship anyway. It s in fact an extremely clear declaration of the ownership contract because while the owner can own many things, an object in this graph can have but one owner.
Now, I'm not sure how you would encode this into a compiler, so the discussion may have to end there as being impractical to implement.
The solution is to implement guis with composition and delegation rather than inheritance. It requires some ree-thinking and elbow grease, rather than just copying stale designs from 30 years ago, which may be why so many people avoid it, though.
I hit the same wall, and opted to learn a new way of design after years of slavishly adhering to OOP.
There's not much that I miss from OOP, and I now see its utility as more about making the ability to program scale predictably with an unpredictable workforce.
You can drop something when you use it some noticeable amount of time, not just couple of weeks. Otherwise it's more "I tried but it's not for me". There's nothing wrong with it - somebody will love Rust, somebody will not, somebody will love even Go - it's ok.
So, you want class based polymorphism, and you want trees of objects with back references. Sounds like Qt to me. I have the feeling the author tried to translate his knowledge of OOP languages to Rust.
Bad idea. Try F# first.
Seriously: my current favourite language is Ocaml, and it would have basically the same problems. But I don't care, because I don't think like that. I have other ways to solve my problems.
The author seems to be confusing typeclass composition with OOP. They are only topically similar. (Personal opinion; typeclass composition is more principled than OOP, but a bit more rigid, which he is running into.)
There are several good ways to do this with type classes/traits. One way is to write a function
font_size : Widget t => t -> FontSize
And then in the widget definition have the "internal" font size defined, which the author seems to be assuming the existence of for some reason.
Additionally, in a language like Rust with ADTs, there's no good reason to return negative numbers to signify things like a missing font. He should be returning Maybe FontSize if he wishes to signify that an object may not contain a font size.
> The author seems to be confusing typeclass composition with OOP.
I agree and would like to expound on that idea. One of my biggest frustrations with inheritance in traditional statically typed languages (I program in C++ for a living) is that inheritance is performing two functions at once: code reuse and typing. Confusing the two seems to cause a lot of pain. Inheritance as a type system is describing the kinds of things the object can do. Inheritance is (usually) a sufficient condition to say that the types can be used interchangeably. Making inheritance the only way to express the type information often forces some very unfortunate code.
The author is trying to use traits as a code re-use mechanism. He wants the trait to be able to see into the implementation and be a function of the implementation's private data. If that were allowed, that would invite all of the pain of inheritance for that kind of trait. Types with a different internal implementation would end up being awkward at best.
I've picked it up and put it down a few times now. It is super humbling to feel like I've been reset to total newb status after decades of work with software, including OO, imperative (with and without GC), declarative and functional languages.
I'm still interested and motivated to learn more. I hope to get a chance to spend time on it at work because a free hour here and there isn't enough for me to absorb this stuff.
And confession time: I haven't even done too much fighting w/the borrow checker, I'm still slowly absorbing the type system and other general stuff.
On trees: Can you use regular references for the children, and weak references for the link back to the parent? That's a valid structure that tears down properly. With strong backpointers, when one of the objects is deleted, there's a moment when there's still a live reference to it. That's invalid under Rust's rules, so you can't do that.
There's also the option to own all tree objects with some collection for allocation purposes, and use weak references for all inter-object links.
As for traits, Rust does seem to have an overly novel and complex type system, and I'm not going to defend it.
The only real difference between a weak and strong pointer is that weak pointers are less prone to leaking memory (in exchange for the possibility of "sorry I'm dead" being a response to accesses). As soon as you upgrade such a pointer you've created a cyclic graph.
He only way for Rust's type system to understand these kinds of access patterns in a way that doesn't require the insertion of "shared mutability" types (e.g. Mutexes) involves mutating the tree during traversals.
A simple version of such a pattern: a "doubly linked" list which is actually two singly linked lists. One "traverses" this list by popping nodes off one and pushing them onto the other. The user has easy access to the heads of both lists. You could probably do the same with trees but this is a pretty unpleasant pattern.
I don't understand why the author wants to access fields from a trait. They can be accessed by the trait implementations, which makes sense, because the fields depend on the type.
It seems like maybe rather that just porting nanogui it might have made sense to do a bit more translation/conversion of the patterns used in it to what rust encourages/makes easy...
Wasn't really able to follow the bit about traits but:
It seems like if you want to have back-references you either would need to use Rc<RefCell<T>> or Arcs, which is not the end of the world.
The best way to do it would be to not have back references at all. Then you could simply have Vec<Box<Widget>> for children.
If I were going to build a GUI system in rust I certainly wouldn't design it to be OO. I think you would repeatedly run into problems. An entity component system or something like react might work better in rust.
> However, a trait has no knowledge of the underlying implementation. So a trait can define abstract functions, but can't access any underlying fields.
> ...
> Let that sink into you. My first reaction to this was "Sorry, what?!". While there are valid criticisms against OOP, to offer this as a solution is just silly.
This is a misunderstanding of what traits do. Interfaces in Java have no knowledge of the underlying implementation, either. And more generally, neither does an ABC: you can't use fields you don't declare in the ABC in methods declared in the ABC.
It is sad that Rust traits can declare only functions, not fields; so you must redeclare your fields as methods if you want to be able to use them as part of a default implementation (I think). Later the author explores this option -- but notes the "field methods" must be public along with the real interface of the trait. I am not sure what options Rust provides for making them private. For example, it might be possible to split the trait into a public and a private half. (But maybe not: https://github.com/rust-lang/rfcs/blob/master/text/0136-no-p...)
I do question the wisdom of the author's design at a higher level, because the author is using a trait for provide an implementation, not just an interface. If the implementation really applies to everything that matches the trait, it should probably be provided with a generic. If not, it is at worst tedious, but hardly limiting, to recycle a generic definition by calling it in an implementation.
I have been feeling that way ever since two years ago when I first looked at the language. I still don't understand why this language has such popularity. It's really bad for the reasons mentioned. C++ is a solid language where you can do everything you want and also write memory safe code in a clean way with unique_ptr. C++ is not perfect but to me no alternative come close yet, rust and go comprised.
C++ is not memory safe, and neither is uniq_ptr. Use after move on one causes a segfault. (Actually IIRC it's UB, but it usually manifests as a segfault)
C++ is not memory safe. unique ptr provides no protection against dangling references. Besides, you cannot use unique ptr to make backreferences, which is what this article is complaining about.
I've been observing in some stronger critics about Rust a persistent focus on OO features, usually the ones that programmers tend to rely upon and which they feel to be "lacking" in Rust - usually requiring a different structure or abstraction which the programmer is not familiar with.
The issue about parent/children pointers mentioned in this article seems more a lack of understanding on the concepts of ownership and the traits system, for instance. It's indeed a harder problem to deal with in Rust if trying to apply a OO mentality, but one can find a way out if trying to understand the language first.
I have my personal thoughts about parts is Rust that could be improved; nevertheless, I don't see any issue with us excursions, and consider it an evolutionary step which brings system programming languages to a new level.
What "non-OO" methods for representing cyclical data structures are you referring to? The only one I can think of is lots of indirection (e.g. how petgraph does it), which can be too slow (especially for systems programming). As far as I can tell, Rust's model for verified code is pretty hostile to cyclical structures, especially if you want inhomogeneous references (i.e. not everything is an edge on a graph). I don't think you can just sweep this under the rug as "lack of understanding" of Rust's better ways of doing things.
Someone should write a good book on Rust's approach to OOP and how it differs let's say from classic C++ when doing actual library design. Despite all the good documentation on Rust, this topic is really lacking clarity.
[+] [-] pcwalton|9 years ago|reply
Go has the exact same set of features Rust has here, no more: struct composition or interfaces/traits.
Regarding graphs: The best solution is option 3, using a crate on crates.io (petgraph is the most popular, but rust-forest is fine too). The preferred solution to having multiple types of nodes in the same tree is to use an enum or a trait. (Servo uses a trait for its flow tree, with downcast methods like as_block() if needed.)
I'm puzzled by the contention that the fact that all nodes have to be the same type is a problem with Rust. That's always true in any language. Even in C, you'd have to have your next/previous pointers have some type, or else you couldn't traverse the list!
[+] [-] mercurial|9 years ago|reply
That's not really a great option for a widget library, because it means no custom widgets.
[+] [-] mcguire|9 years ago|reply
Or, couldn't you have a vec that owns the nodes and use indices instead of pointers? Or use pointers with the lifetime of the vec?
[+] [-] skywhopper|9 years ago|reply
[+] [-] Lazare|9 years ago|reply
"Right, so they've been working on this potential change since 2016, and...wait. 2016?!"
If he'd said 2014 or earlier, I'd consider him to have a point. 2015 is asking for a bit too much, but I can still see his frustration; I'd call that a wash. But 2016? It destroyed his credibility with me, because it's clear he has no idea what he's talking about when it comes to developing a programming language.
I know that sounds harsh but...man. He's leaving Rust because they're taking more than six months to implement a massive change to a core language mechanic? Have fun working with absolutely any other language ever.
[+] [-] stouset|9 years ago|reply
It hasn't languished, it's not just a bunch of +1s met with developer silence. What precisely is the author expecting the Rust team to do better on here?
[+] [-] exadeci|9 years ago|reply
Not that it's a bad thing but if he does programming as a hobby it means that he only used it a few hours a day for 6 months.
I don't quite understand why HackerNews gave so much importance to some random developer opinion on Rust.
[+] [-] chaostheory|9 years ago|reply
> Rust's ecosystem, since Rust itself is reasonably young, is still in a developing stage.
> What I didn't know though, was that when you hit a road-block, finding a solution feels like navigating a jungle.
Living on the edge always has a price
[+] [-] Agathos|9 years ago|reply
[+] [-] xuejie|9 years ago|reply
I might be wrong but judging from this, I felt like we might never reach a conclusion on this topic even in a considerably long enough time, since it did seem to me that the core team is not interested in this feature.
From this point of view, I don't think it matters too much when we debate if six month is long enough, even if we waited longer, I'm not sure if the situation is gonna change when interest in this feature stays low.
[+] [-] ekidd|9 years ago|reply
> So a trait can define abstract functions, but can't access any underlying fields.
In Rust, a trait is a purely abstract interface to a type. It explains how you can use that type, but it knows nothing about the implementation.
I'm not quite sure what the design goal was with the Widget type, but the closest solution is to replace the member variable in the abstract type with some kind of accessor (and to replace the magic negative font sizes with an Option):
I'm not sure this is how I'd personally design this, but it should compile.> What IS the idiomatic Rust way to do a cyclical directed graph?
Unfortunately, the correct idiomatic way is that you try very hard to avoid doing so. Rust is all about clear ownership, and it doesn't like circular references.
The usual solution is to put the cyclic graph code into a library, and to use pointers and about 20 lines of unsafe code. (EDIT: See below for a better solution.) It's not much different from the C++ solution.
The Servo team really wants GCed, safe pointer support in Rust, and design work is well underway. But it's going to take a while to stabilize.
GUI libraries are an interesting special case: They involve huge class hierarchies, lots of implementation inheritance, circular references, and tricky ownership. Most older OO languages were optimized for this case. Newer languages tend to favor flatter type hierarchies and far less implementation inheritance. Which means that traditional object-oriented GUI designs may be awkward.
[+] [-] pcwalton|9 years ago|reply
Well, petgraph uses vectors and indices to avoid unsafe code, and it's the most popular graph library on crates.io.
Really, the answer here is "use a graph crate on crates.io". If you don't know which one to use, the best answer is probably petgraph.
> The Servo team really wants GCed, safe pointer support in Rust, and design work is well underway. But it's going to take a while to stabilize.
We aren't going to use GC'd pointers for any of the trees except the DOM. GC is way overkill for this use case. The reason why we want GC support is not to make tree data structures: it's for integration with JavaScript.
[+] [-] loup-vaillant|9 years ago|reply
I doubt they have to be. There are other ways to do GUI, see for instance how Light Table uses a database-like Entity Component System. https://www.youtube.com/watch?v=V1Eu9vZaDYw
[+] [-] partiallattice|9 years ago|reply
> Unfortunately, the correct idiomatic way is that you try very hard to avoid doing so. Rust is all about clear ownership, and it doesn't like circular references.
> The usual solution is to put the cyclic graph code into a library, and to use pointers and about 20 lines of unsafe code.
The point about C++ is important. Cyclic graphs are hard in any non-GC'ed language because it breaks the ownership story.
[+] [-] hinkley|9 years ago|reply
UIs work routinely by the organizational principle the author is attempting. Nested groupings of related functions that often interact in small groups.
And the thing is, a pointer to your parent doesn't semantically mean an ownership relationship anyway. It s in fact an extremely clear declaration of the ownership contract because while the owner can own many things, an object in this graph can have but one owner.
Now, I'm not sure how you would encode this into a compiler, so the discussion may have to end there as being impractical to implement.
[+] [-] mcguire|9 years ago|reply
Any details on this? GCs and finalizers don't really work well together, which is why I gave up on my cycle detector implementation.
[+] [-] jwatte|9 years ago|reply
[+] [-] dilann|9 years ago|reply
> traditional object-oriented GUI designs may be awkward
For example, Qt has moved that tricky part into a specialized language. I think that it's the way to go.
So it's okay if a low-level language is not designed to do that ancient stuff with pointers to widgets.
[+] [-] fungos|9 years ago|reply
I understand his rant, but I don't think it is a valid one. You must change your design or change your tool, he opted to change the tool.
[+] [-] firebones|9 years ago|reply
There's not much that I miss from OOP, and I now see its utility as more about making the ability to program scale predictably with an unpredictable workforce.
[+] [-] EugeneOZ|9 years ago|reply
But title is too dramatizing.
[+] [-] amq|9 years ago|reply
[+] [-] loup-vaillant|9 years ago|reply
Bad idea. Try F# first.
Seriously: my current favourite language is Ocaml, and it would have basically the same problems. But I don't care, because I don't think like that. I have other ways to solve my problems.
[+] [-] mcguire|9 years ago|reply
[+] [-] wyager|9 years ago|reply
There are several good ways to do this with type classes/traits. One way is to write a function
font_size : Widget t => t -> FontSize
And then in the widget definition have the "internal" font size defined, which the author seems to be assuming the existence of for some reason.
Additionally, in a language like Rust with ADTs, there's no good reason to return negative numbers to signify things like a missing font. He should be returning Maybe FontSize if he wishes to signify that an object may not contain a font size.
[+] [-] partiallattice|9 years ago|reply
I agree and would like to expound on that idea. One of my biggest frustrations with inheritance in traditional statically typed languages (I program in C++ for a living) is that inheritance is performing two functions at once: code reuse and typing. Confusing the two seems to cause a lot of pain. Inheritance as a type system is describing the kinds of things the object can do. Inheritance is (usually) a sufficient condition to say that the types can be used interchangeably. Making inheritance the only way to express the type information often forces some very unfortunate code.
The author is trying to use traits as a code re-use mechanism. He wants the trait to be able to see into the implementation and be a function of the implementation's private data. If that were allowed, that would invite all of the pain of inheritance for that kind of trait. Types with a different internal implementation would end up being awkward at best.
[+] [-] exDM69|9 years ago|reply
I really like traits/type classes over OOP-style type systems but sometimes it causes a bit of impedance mismatch.
[+] [-] unknown|9 years ago|reply
[deleted]
[+] [-] wyldfire|9 years ago|reply
I'm still interested and motivated to learn more. I hope to get a chance to spend time on it at work because a free hour here and there isn't enough for me to absorb this stuff.
And confession time: I haven't even done too much fighting w/the borrow checker, I'm still slowly absorbing the type system and other general stuff.
[+] [-] Animats|9 years ago|reply
There's also the option to own all tree objects with some collection for allocation purposes, and use weak references for all inter-object links.
As for traits, Rust does seem to have an overly novel and complex type system, and I'm not going to defend it.
[+] [-] Gankro|9 years ago|reply
He only way for Rust's type system to understand these kinds of access patterns in a way that doesn't require the insertion of "shared mutability" types (e.g. Mutexes) involves mutating the tree during traversals.
A simple version of such a pattern: a "doubly linked" list which is actually two singly linked lists. One "traverses" this list by popping nodes off one and pushing them onto the other. The user has easy access to the heads of both lists. You could probably do the same with trees but this is a pretty unpleasant pattern.
[+] [-] pcwalton|9 years ago|reply
[+] [-] whyever|9 years ago|reply
[+] [-] steveklabnik|9 years ago|reply
[+] [-] hardwaresofton|9 years ago|reply
[+] [-] santaclaus|9 years ago|reply
I'm curious if anyone has tried developing a Qt5 app in Rust -- are there any Rust-ic bindings out there?
[+] [-] mastax|9 years ago|reply
[+] [-] robohamburger|9 years ago|reply
It seems like if you want to have back-references you either would need to use Rc<RefCell<T>> or Arcs, which is not the end of the world.
The best way to do it would be to not have back references at all. Then you could simply have Vec<Box<Widget>> for children.
If I were going to build a GUI system in rust I certainly wouldn't design it to be OO. I think you would repeatedly run into problems. An entity component system or something like react might work better in rust.
[+] [-] solidsnack9000|9 years ago|reply
This is a misunderstanding of what traits do. Interfaces in Java have no knowledge of the underlying implementation, either. And more generally, neither does an ABC: you can't use fields you don't declare in the ABC in methods declared in the ABC.
It is sad that Rust traits can declare only functions, not fields; so you must redeclare your fields as methods if you want to be able to use them as part of a default implementation (I think). Later the author explores this option -- but notes the "field methods" must be public along with the real interface of the trait. I am not sure what options Rust provides for making them private. For example, it might be possible to split the trait into a public and a private half. (But maybe not: https://github.com/rust-lang/rfcs/blob/master/text/0136-no-p...)
I do question the wisdom of the author's design at a higher level, because the author is using a trait for provide an implementation, not just an interface. If the implementation really applies to everything that matches the trait, it should probably be provided with a generic. If not, it is at worst tedious, but hardly limiting, to recycle a generic definition by calling it in an implementation.
[+] [-] forrestthewoods|9 years ago|reply
That's a great question! So, uh, what is the answer?
[+] [-] GFK_of_xmaspast|9 years ago|reply
[+] [-] arbre|9 years ago|reply
[+] [-] steveklabnik|9 years ago|reply
[+] [-] plandis|9 years ago|reply
[+] [-] pcwalton|9 years ago|reply
[+] [-] jwatte|9 years ago|reply
[+] [-] jdc2172|9 years ago|reply
Perhaps std::mem::forget
[+] [-] leovonl|9 years ago|reply
The issue about parent/children pointers mentioned in this article seems more a lack of understanding on the concepts of ownership and the traits system, for instance. It's indeed a harder problem to deal with in Rust if trying to apply a OO mentality, but one can find a way out if trying to understand the language first.
I have my personal thoughts about parts is Rust that could be improved; nevertheless, I don't see any issue with us excursions, and consider it an evolutionary step which brings system programming languages to a new level.
[+] [-] johncolanduoni|9 years ago|reply
[+] [-] shmerl|9 years ago|reply