
Why I’m dropping Rust - posthoctorate
https://medium.com/@kingoipo/why-im-dropping-rust-fd1c32986c88
======
pcwalton
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!

~~~
mercurial
> The preferred solution to having multiple types of nodes in the same tree is
> to use an enum

That's not really a great option for a widget library, because it means no
custom widgets.

~~~
derkha
But widgets do have the same type: Widget, which the author already uses as a
trait object. I think he was getting confused by mixing &Widget and
Rc<RefCell<Widget>>. Just declare a type alias for the latter, use it
everywhere, and it should work. You would do the about the same thing in C++,
I believe.

------
skywhopper
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.

~~~
Lazare
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_.

~~~
andars
> Traits as a concept have been part of the language for numerous years. It's
> currently September 2016.

Reads to me like he is disappointed that they didn't start the process sooner
and have just recently started to discuss a solution.

------
ekidd
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.

~~~
pcwalton
> 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.

~~~
TillE
A lot of problems in Rust seem to involve replacing (smart) pointers with
handles. Sometimes you really do want to do that, but it's bad if it's a
necessary kludge.

Like someone else said recently, it's a problem if you can't reasonably teach
a basic computer science course in Rust.

~~~
ekidd
"Safe" Rust works for almost everything in a typical CS curriculum.

But if you want to teach an operating systems course or build cyclic, pointer-
based data structures, you'll usually need a modest amount of "unsafe" Rust.
Using "unsafe" gives you access to real pointers, which work just like they do
in any systems language.

It's just that Rust chooses to lock those features away when you don't
explicitly ask for them. If you actually need "unsafe", it's no more dangerous
or more difficult than writing C code.

~~~
jblow
Where did you learn CS that they didn't have lots of cyclic data structures?
We had those even in our intro class at Berkeley...

~~~
coldtea
I a few universities I know, including my own, cyclic data structures are on
the curriculum, but nowhere near being taught as basic or intro stuff. That
would lists, queues, trees, and hash-maps.

~~~
desdiv
The regular LinkedList in Java is implemented as a doubly linked list, which
is cyclic.

------
fungos
It seems the author is trying to do classical OOP using traits (type classes).
They aren't a match although the similarities.

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
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.

------
EugeneOZ
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.

But title is too dramatizing.

~~~
amq
> somebody will love even Go

------
loup-vaillant
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.

~~~
mcguire
Ocaml has a GC and supports class based inheritance, right?

~~~
loup-vaillant
> _Ocaml has a GC_

Yes, but the language is immutable and eager by default. That makes circular
references a chore.

> _and supports class based inheritance_

Yes, but hardly anybody uses that part of the language. We tend to stick with
modules and variations over them.

~~~
mookid11
> Yes, but hardly anybody uses that part of the language. We tend to stick
> with modules and variations over them.

You can have inheritance via polymorphic variants: see Garrigue's `Code reuse
through polymorphic variants`.

~~~
loup-vaillant
I have never used polymorphic variants… Too lazy to learn.

------
wyager
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.

~~~
partiallattice
> 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.

~~~
indolering
This sounds like a failure of the Rust documentation.

~~~
wyager
The documentation is supposed to tell you what the language is, not
exhaustively detail everything the language is _not_.

~~~
indolering
No, it's supposed to teach people how to use the language. When a large
fraction of your user base will be bringing a specific skill set, then you
should generate documentation aimed at translating those skills!

~~~
wyager
You've described tutorial, not a language documentation set.

------
wyldfire
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.

~~~
chris_st
My thoughts exactly.

My confession: I've fought the borrow checker and lost a few times, and just
scaled back what I wanted to do, so far.

Maybe someday I'll know what I'm doing :-) but I do like it so far, certainly
more than C++.

~~~
firebones
Just keep plugging away and finish something. It took me three projects to get
to the point where the cognitive load of what I _no longer_ had to worry about
exceeded what I had to worry about.

It's hard to describe, and to say "if it compiles, it works" is not 100%
accurate, but I would say "if it compiles, it works, and if it doesn't work,
then I know it's my own logic error". It's like Rust guides you into the space
of correct solutions and cleaner code when compared to other languages.

The other thing: there are huge dividends you reap from spending time in Rust
that are realized when you go back to other languages. If you've spent your
life in managed runtimes or more loosely-typed languages, you come away better
for the time spent in Rust.

~~~
cfallin
> the cognitive load of what I no longer had to worry about exceeded what I
> had to worry about.

Yup, this has been my experience too. I started using Rust for my primary
project last March and was worried a little at first about the risk of wasting
too much time, but at this point I'm ~1.5x to 2x as productive as in C++11:

* essentially no more "head-scratching action-at-a-distance bugs" (I've spent N years in C++ and I _still_ sometimes invalidate an iterator) -- as you say, any errors that still exist are higher-level logic errors, which are (in my experience) much more straightforward to track down.

* enums and match/destructuring and all the builtin types (e.g. Option, Result) have me thinking in a much more strongly-typed and principled way. So even logic errors tend to be less frequent because I break down the problem the 'right' way immediately. Algebraic datatypes let one describe a state space really succinctly.

Rust is one of those languages that really, strongly wants you to be
idiomatic, but it pays off!

------
Animats
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.

~~~
Gankro
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.

~~~
lmm
Tree zippers can be pretty nice to work with, no?

------
whyever
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.

~~~
steveklabnik
You should check out the RFC they linked; there are good reasons to enable
this.

~~~
whyever
Thanks! Here are the reason why trait fields are preferable over accessors in
case anyone else is wondering:

1\. The borrow checker assumes accessors can do anything, freezing all other
fields. This does not happen with trait fields. 2\. Accessors have to use
dynamic dispatch in case of trait objects, resulting in worse performance.

------
hardwaresofton
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...

------
santaclaus
> For other use cases, Rust is still lacking. One such use case would be an
> OpenGL GUI, like CEGUI or nanogui.

I'm curious if anyone has tried developing a Qt5 app in Rust -- are there any
Rust-ic bindings out there?

~~~
mastax
[https://github.com/cyndis/qmlrs](https://github.com/cyndis/qmlrs) is pretty
good

------
robohamburger
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.

------
solidsnack9000
> 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...](https://github.com/rust-
lang/rfcs/blob/master/text/0136-no-privates-in-public.md))

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
> what IS the idiomatic Rust way to do a cyclical directed graph?

That's a great question! So, uh, what is the answer?

~~~
GFK_of_xmaspast
Some kind of adjacency list combined with a standard container?

------
arbre
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.

~~~
steveklabnik
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)

~~~
trimbo
It looks like clang-tidy just committed a check for this case:
[https://reviews.llvm.org/D23353](https://reviews.llvm.org/D23353)

~~~
steveklabnik
Not exactly this case. From the link:

    
    
      > No warnings are emitted for objects of type ``std::unique_ptr`` and
      > ``std::shared_ptr``, as they have defined move behavior.
    

Regardless, I am happy to see more static checking for C++, and am following
the GSL/Core Guidelines closely. It's important work.

------
jwatte
The very first complaint seems to totally miss how traits are interfaces, not
classes. Doesn't make enough of an impression to keep going...

------
jdc2172
"There's simply no way to tell the compiler to not drop a variable
automatically when it goes out of scope."

Perhaps std::mem::forget

------
leovonl
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.

~~~
johncolanduoni
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.

------
shmerl
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.

------
micro_softy
Why I'm dropping Rust (hackernoon.com)

Why the redirect from medium.com?

------
beguiledfoil
>In languages such as C++ or C#, this is an easily solved problem. You create
an abstract class or a base class depending on language and inherit that in
your actual class.

Oh cool an anti-pattern being described as an "easy solution".

------
snarfy
I wonder how many of his problems could have been solved using the 'unsafe'
keyword. I'm naively familiar with Rust through the various news postings, but
know it has the unsafe escape hatch.

------
dragon_ninja
If tired of Rust, you might try Crystal:

[https://crystal-lang.org/](https://crystal-lang.org/)

[https://github.com/veelenga/awesome-
crystal](https://github.com/veelenga/awesome-crystal)

------
lossolo
I think it would be fair if you also would disclose that you are working in
Mozilla and that you are directly connected to Rust when defending it on HN.

~~~
MaulingMonkey
Sounds like overkill to me? Would a comment in a profile be sufficient?

(Disclaimer: I'm working on a web based brainfuck ide/compiler/optimizer for
my own amusement, and thus probably have some kind of dog in some kind of
fight here?)

~~~
eternalban
I think an affliation heads-up is required if you are pissing on a
competitor's product. And in such cases, HN does effective policing.

It is also possibly helpful if the commenter is elaborating on product that
they are involved in to establish that their input is authoritative.

And not to give cause for inflated heads here :) but seriously, if you are
seriously interested in this space, you sh/would already know the cast of mvps
and thus major contributors such as Russ Cox or Patrick Walton (et al) would
not require introductions.

------
decafbad
Why would I care?

