
My experience rewriting Enjarify in Rust - buster
https://medium.com/@robertgrosse/my-experience-rewriting-enjarify-in-rust-723089b406ad#.wdxskueoh
======
the_duke
Good write up.

I can agree with him on a couple of points.

I've found Rust code to become terribly verbose and unreadable for certain
tasks. You have to be careful to not end up with messy and hard to read code.
Lifetimes of course make it even worse. Some common patterns are hard /
awkward to implement. The stdlib is (intentionally) very small, and throwing
together various crates adds a considerable maintenance overhead. Overall I
find Rust really promising though. It might need a few more years to mature.

I totally agree on Go: I intially really loved it and used it all the time.
The more code, and especially the more reusable libraries I wrote, I started
hating it more and more, to the point where I only touch Go if I have to, or
for small, one-off, self contained projects that need a bit more perf than
Python or where Haskell is not appropriate (team, problem space).

The only thing that's a plus for go is it's dead-easy concurrency.

~~~
sethammons
I've not looked at Rust, but I've been using Go in production now for years at
this point with non-trivial code bases. In our cases, they are mostly SMTP and
HTTP servers. Sometimes the boiler plate gets in the way (and I think Go
should almost never be used for simple CRUD stuff, there are better options),
but I find the readability fantastic. It really dawned on my how readable it
can be when I had to fix a missed condition in clean up on shutdown. It was
fixed in an hour. For the perl version that the Go one replaced, it would have
been a day before I even understood what would have needed fixing.

~~~
ngrilly
> I think Go should almost never be used for simple CRUD stuff

Why?

> there are better options

Which ones?

~~~
goatlover
PHP, Ruby, Javascript, Python - pick one.

~~~
duaneb
Why are they better? Dynamic languages are orthogonal to creating CRUD apps
and come with massive runtime and reading overhead.

~~~
goatlover
The overhead doesn't matter for simple CRUD stuff. The developer's time is
what matters. The dynamic language are more productive.

~~~
duaneb
> The dynamic language are more productive.

Do you have a source for that? I hear it a lot and I just don't buy it.

~~~
slmyers
Isn't it more of an opinion than an objective truth? ie, it doesn't really
need a source.

~~~
duaneb
That is what I was angling toward.

------
tomjakubowski
> If you want to be able to refer to the contents of the enum, have named
> fields, or call methods, you need to create a separate struct definition.

Not so! An enum variant may contain named fields. From The Book,
[https://doc.rust-lang.org/book/enums.html](https://doc.rust-
lang.org/book/enums.html) :

    
    
        enum Message {
            Quit,
            ChangeColor(i32, i32, i32),
            Move { x: i32, y: i32 },
            Write(String),
        }

~~~
the_duke
I agree with him though that enum variants not being distinct types is a
hassle.

I think there's an RFC for making this possible.

~~~
lmm
It's by design AIUI; certainly it's often what you want (I won't claim
"always", but probably more often than actually wanting the distinct
subtypes).

~~~
gamegoblin
Even Haskell needs an explicit language extension flag to make this happen
(-XDataKinds I believe).

Consider this use case

    
    
        data Pointer = NullPointer | NonNullPointer Int
    

And I want a function that only accepts non-null pointers:

    
    
        dereference :: NonNullPointer -> a
    

To do that and maintain my Pointer type, I'd have to do something like:

    
    
        data NonNullPointer = NonNullPointer_ Int
    
        data Pointer = NullPointer | NonNullPointer NonNullPointer
    
        --                           ^ The namespace of constructors and types is distinct,
        --                             so this is fine and is a constructor of Pointer that
        --                             contains a NonNullPointer, which only
        --                             has one constructor, NonNullPointer_ (note the _)
    

I believe -XDataKinds does something like this behind the scenes, but someone
better than I should verify. I just use the above technique rather than
questionable language extensions.

~~~
dllthomas
DataKinds lets you use NullPointer and all the various NotNullPointer values
as types, but they are uninhabited. It doesn't (obviously) let you restrict a
parameter to values using a particular constructor of a sum type.

~~~
gamegoblin
Just looked it up and it was GADTs I was thinking of that would allow me to do
something like that.

~~~
dllthomas
Ah, yes.

------
steveklabnik
I'm always glad to get feedback, we're specifically going to focus on
ergonomics and smoothing rough edges this year, so this kind of thing is
invaluable.

There's some good discussion on the Rust subreddit:
[https://www.reddit.com/r/rust/comments/53ft4m/my_experience_...](https://www.reddit.com/r/rust/comments/53ft4m/my_experience_rewriting_enjarify_in_rust/)

~~~
cm3
Off-topic, but since you're here, let me ask: where can I track the progress
on rustup support for musl-based distros where musl is the host (void linux,
alpine linux) and FreeBSD? I got everything but Rust working nicely on void
linux, and it's the only thing holding me back right now. Since Docker
defaults to alpine linux, the reach of such support will be wider than one
might initially think.

~~~
steveklabnik
For void: [https://github.com/voidlinux/void-
packages/issues/4570](https://github.com/voidlinux/void-packages/issues/4570)

And alpine:
[http://pkgs.alpinelinux.org/packages?name=rust*&branch=&repo...](http://pkgs.alpinelinux.org/packages?name=rust*&branch=&repo=&arch=&maintainer=)

[https://github.com/rust-
lang/rust/issues/31322#issuecomment-...](https://github.com/rust-
lang/rust/issues/31322#issuecomment-247818983) is the latest news on the
subject. It's not impossible, but we're not sure _how_ we want to make it
easy. The real issue is the bootstrapping, my understanding is that cross-
compiling a musl compiler from a gnu compiler does work today, which is how
those packages were made.

~~~
cm3
Thanks for the pointers. Yeah, after you've bootstrapped once, you have a
version to bootstrap another, especially since the new requirements to not
depend on a nightly snapshot. rustup should be able to provide images that way
which work on both alpine and void. The default would be a dynamically linked
against musl rustup image which may still expose musl-static as a target just
like it does on glibc hosts. I wouldn't complicate matters as a first step
with questions of whether a musl image should be statically linked.

------
Roboprog
It's a shame there aren't more "Modula" derived modern languages, rather than
trying to force C into being a higher level language.

Java, especially at the 1.1 mark, felt like some kind of crippled version of
Borland's Pascal/Delphi + a UCSD P-system VM (dressed up to look like C++).

Microsoft for C# hired the language designer from Borland.

The Go team includes a Uni of Zurich guy, and at least decided to use a
declaration syntax (when variable types aren't inferred) more like Pascal.

Start with a language that wasn't a shiv with a broken handle in the first
place to build applications.

It's a shame Eiffel never took off instead of the wretched C++, no, this time
for sure, Java... (not, that's not it, either - lather, rinse, repeat)
lineage.

(C was vastly preferable to assembly in the late 80s / early 90s, but it's
time to let it go, and its syntax/semantics, people)

Irony: I like the unix philosophy of composing processes in a functional
pipeline, which is totally lost in the era of monolithic java crud, but that
is another huge tangent.

RANT ENDS.

~~~
Hemospectrum
The Rust community is pretty good at turning specific complaints into
actionable proposals. Could you perhaps be more specific about what nice
things in Modula/Delphi/Eiffel are missing from other languages, or about what
might be different if Go or Rust borrowed more of their influence?

~~~
Roboprog
Thanks for the response, it was just a rant.

I suspect the Rust community and I simply have irreconcilable differences.
They want a better C or C++, and like special character ditties such as &, *,
<_> (whatever that does), and I would rather have short keywords in some
places.

For example, declaring a parameter with "var" in a function heading means it
is a reference, instead of a copy. However, there is no dereferencing syntax,
you simply use it as if it were a local "auto"/stack type variable (which also
cannot be a null, so no checks are required - which Rust probably takes care
of as well if you ask nicely), and there is nothing special for the caller to
do, unless the caller has a pointer that needs to be checked and then
dereferenced in the call.

I also miss reasonable "set" operations, even if the sets were just bit
vectors on short enumerated types (enums or short sub-ranges of other discrete
values). Maybe Rust has sane set operations, but Java's collections operations
are a bit off: mutating "add-all" vs union producing a new set, and so on.

Eiffel's pre/post-condition and class invariant special assertions are nice,
as well.

~~~
aweinstock
Rust's set types do implement the BitAnd and BitOr traits, which allow using
the & operator for intersection, | for union (There's also a few others, like
(Sub, -, set difference) and (BitXor, ^, symmetric difference)).

They use Clone to construct a new set for the result, for use with a
functional style. There are also the union/intersection methods that produce
lazy iterators over references to the values in the sets, for cases where
cloning the set elements would be too big a runtime price, at a slight cost to
ergonomics.

Clone-based operator overloading:

[https://doc.rust-
lang.org/std/collections/struct.HashSet.htm...](https://doc.rust-
lang.org/std/collections/struct.HashSet.html#method.bitand)

[https://doc.rust-
lang.org/std/collections/struct.HashSet.htm...](https://doc.rust-
lang.org/std/collections/struct.HashSet.html#method.bitor)

Lazy-iterator-based methods:

[https://doc.rust-
lang.org/std/collections/struct.HashSet.htm...](https://doc.rust-
lang.org/std/collections/struct.HashSet.html#method.intersection)

[https://doc.rust-
lang.org/std/collections/struct.HashSet.htm...](https://doc.rust-
lang.org/std/collections/struct.HashSet.html#method.union)

Do these qualify as reasonable set operations?

------
Fede_V
This was exceptionally well written, and it closely mirrors my experience as a
python programmer slowly moving into Rust.

Rust is awesome, but there are a few rough corners where you end up having to
write really un-ergonomic code.

~~~
blub
After reading this article my conclusion is that Rust is some kind of static-
typed Perl with a focus on memory safety.

The syntax is horrid.

steveklabnik asked me once why I thought Rust is more complex than C++. Well,
if one takes a look at any of the Rust snippets in this article without being
deeply disturbed by the @'s the .'s, &'s and *'s, then we have a completely
different idea about what it means to build a user-friendly and ergonomic
programming language.

Luckily I don't have to pick between Rust and Go, but if I had to I would just
stick to Python and use type annotations after reading this.

~~~
steveklabnik
In a systems language, it's important to know if you're passing around things
by value or reference. We follow the Python maxim here: explicit is better
than implicit. This means &s to indicate where something is passed by a
pointer, and * for dereferencing. This is the same as C++, we just do less
magic coercions. We also tried to not make up too much syntax, and stick to
"curly braces and semicolons" for most things. Even lifetime syntax isn't new,
though OCaml uses it for a very different purpose than we do. (There also
aren't any @s in there, unless I missed something? And . is the same as in any
language: for method calls, and we made it _simpler_ than C or C++ here, by
having . instead of . and ->.)

I agree that you can make a language without many syntactical constructs, but
Rust has other goals as well. Language design is hard! There's tons of tension
between various choices.

~~~
atombender
Rust is exceptionally noisy and terse. Easily worse than C++, in my opinion,
which is known for being noisy.

Two things bug me in particular. Rust eschews readability by erring on the
side of terseness — fn instead of something like func, for example, might save
some typing, but does contribute to making visually denser.

The choice of snake_case instead of camelCase also adds to the noisiness,
because there's no programmer font that aligns the underscore with the text
baseline, so it's disruptive. Snake case is pretty aptly named;
typographically, there's a lot more visual cohesiveness to camelcase. (As an
aside, I never understood the lack of consistency here: the capitalized
version of foo_bar should surely be Foo_Bar, not FooBar.) Python admittedly
uses snakecase, but avoids looking too ramshackle by having otherwise very
little noise.

Compare something in Rust (from OP's repo):

    
    
        fn type_list<'a>(dex: &'a DexFile<'a>,
          off: u32, parse_cls_desc: bool) -> Vec<&'a bstr> {
    

to Go:

    
    
        func typeList(dex *DexFile,
          off uint32, parseClassDesc bool) []BStr {
    

I'm not the world's biggest fan of Go, but they did nail the readability
aspect. Nim is another good example which does it even better.

Of course, Go doesn't have lifetime annotations, but I can't understand who
thought using ' was a great idea.

I wish more time was spent on the visual aspect of a language. I'm really
liking Reason, Facebook's new syntax for OCaml, which shows that a language
can be made fresh and friendlier through aesthetics alone. Elixir is cleaner
than Erlang, but I think it went too far in emulating Ruby; instead of very
simply aligning "def" and "end", for example, you now have "do ... end"
everywhere).

Edit: Typo.

~~~
pcwalton
Your criticism comes down to wanting a different abbreviation for "function"
and wanting camel case instead of snake case. I would suggest that it's not
that we didn't spend time on the "visual aspect" of a language: rather it's
that we made choices that were not your preference. We had to pick something
and disappoint somebody; I apologize that that person was you.

> Compare something in Rust (from OP's repo):

As with so many of these comparisons, you're effectively comparing a language
with GC to a language with safe manual memory management, and misinterpreting
this as evidence of bad syntactic choices. (Same with Nim: Nim has GC.)

Look at the function signature when you remove the lifetime annotations:

    
    
        fn type_list(dex: &DexFile, off: u32, parse_class_desc: bool) -> Vec<bstr>
    

When you do that, there's very little difference between that and Go.

> Of course, Go doesn't have lifetime annotations, but I understand who
> thought using ' was a great idea.

Me. It was chosen because it's very visually lightweight.

~~~
atombender
> Your criticism comes down to ...

No, those were only the two most obvious, easiest-to-solve examples that came
to mind.

With all due respect to you and your Rust colleagues (who have done a lot of
impressive work), "we made choices that were not your preference" sounds a
little condescending. These are my personal opinions, just as your
counterarguments are opinions. We should all able to make technical arguments
without resorting to that kind of rhetoric. _Of course_ you made choices.
Everyone here is entitled to disagree with them and explain why.

There's a false dichotomy between "hard-to-read syntax" and "no syntax at all"
being implied here. It's reasonable to imagine that Rust could have had _less
terse_ , but still readable, syntax, even for things like lifetimes.

~~~
pcwalton
> We should all able to make technical arguments without resorting to that
> kind of rhetoric.

The subtext here is that these decisions aren't technical. They're pretty
arbitrary: "func" vs. "fn" or camel case vs. snake case. There's no science
here: there are lots of very successful languages with camel case and lots of
very successful languages with snake case.

I certainly appreciate that you have specific reasons for your preferences,
but I take issue with the idea that we didn't spend time on the visual aspect
of the language.

> It's reasonable to imagine that Rust could have had less terse, but still
> readable, syntax, even for things like lifetimes.

Do you have a proposal?

There were long threads with different proposals back when this stuff was
being designed, but none of them were particularly palatable. The verbosity
with anything that wasn't as lightweight as possible turned out to be too
much.

------
FraaJad
I wish someone would do this in nim and compare the results.

My wishful thinking is that nim would come in at the same number of lines as
Python and very close to Rust's performance.

~~~
agentgt
I was thinking the same thing with OCaml. I wrote in OCaml years ago and I
still remember how incredibly terse the language is/was. You can accomplish so
much with very few lines of code (with the minor exception of the whole
reduplicate signatures and way too many artifacts (mli, cmi, ml, etc)). It
also compiles much faster than Rust (I'm not sure about Go but since Go
doesn't have that sophisticated type system it isn't really fair).

I have to say Rust got fairly hard to read and think about writing code with
lifetimes when using closures. I couldn't decide at times whether to just use
a trait with some cells or get the closure stuff working. This was in part
because of some bugs (about 1.5 years ago) and the fact that their are 4 types
function things. It is also probably because I haven't programmed in anything
analogous that has lifetimes (I haven't programmed in C++ in more than 1.5
decades).

~~~
spion
There is the Python -> OCaml story for 0install:

[http://roscidus.com/blog/blog/2014/06/06/python-to-ocaml-
ret...](http://roscidus.com/blog/blog/2014/06/06/python-to-ocaml-
retrospective/)

This article in particular tackles polymorphic variants
[http://roscidus.com/blog/blog/2014/02/13/ocaml-what-you-
gain...](http://roscidus.com/blog/blog/2014/02/13/ocaml-what-you-gain/) which
solve one of the issues that the author had with Rust (enum subsets)

~~~
nickpsecurity
I recently used that nice article in a discussion on Lobste.rs given its Type
System section does a good job showing what good, static typing can catch
without developer thinking to test for it. Get in habit of specifying basic
stuff right then the compiler pulls its weight a lot. On top of it, key
components in ML's get formally verified on occasion due to their easier
modeling in theorem provers, etc. The last benefit is the compilation is
apparently straight-forward and easier to get right given Esterel managed to
certify SCADE code generator in it. That requires source-to-object code
equivalence proof which is a bit hard in some of these popular languages. ;)

------
catnaroek
You can convert an `Option` into a `Result` and then use the `try!` macro, all
in a single line:
[http://stackoverflow.com/a/37890739/46571](http://stackoverflow.com/a/37890739/46571)

~~~
glandium
But Iterator's next() returns an Option, not a Result.

~~~
catnaroek
I don't see why you'd want to manipulate iterators by hand, unless you're
implementing custom iterators. But, in any case:

    
    
        try!(iter.next().ok_or("end of sequence"))
    

EDIT: Sorry. I was just being dense.

~~~
glandium
Check out the code that goes along the "no try! for Option" comment in the OP:
it _is_ implementing a custom iterator.

~~~
catnaroek
Oh, my bad. I guess `try!` isn't a good enough replacement for properly
treating `Option` and `Result` as monads.

------
p0nce
> Besides, when nearly every call requires a cast, people will just
> automatically add them to shut the compiler up, defeating the purpose.

That's why there is little reason to "fix" C integer promotion rules in the
first place (apart from unsigned).

------
Thaxll
Rust syntax is horrible compare to Python / Go, I don't see how you could give
an advice to someone that do Python to use Rust, you lose GC and add a great
layer of complexity / learning curve. No thanks. Seriously when I see that:

struct IRBlock<'b, 'a: 'b> { pool: &'b mut (ConstantPool<'a> \+ 'a), // other
fields omitted }

~~~
hokkos
GC is not a feature in all cases, and without GC you must manually handle
allocation and deallocation in some cases, C or C++ let you free of lifetime
annotations at the cost of crashes, Swift force you to use counted pointers
and Rust force you in a semantic that forbid errors at the cost of some
lifetime allocation that you consider ugly. But it is still shorter than copy
pasted Go code to simulate generics, and multiple "err, res" that stains the
code.

~~~
Thaxll
My point is a lot of people use Python because it's a simple and powerful
language, Rust is not a simple language at all. And the performance it brings
is not enough to make the decision to switch language in most cases.

~~~
kbenson
I'm not sure what you're arguing. The article didn't advise people to switch
from Python to Rust. It simply gave information that people that are
considering it. The last paragraph even echoes some of your sentiment.

> And the performance it brings is not enough to make the decision to switch
> language in most cases.

That's assuming a lot. Nearly 10x the speed is extremely useful for some
people. Presumably if you are rewriting your Python code in Rust or Go, you've
already identified performance as a big reason to do so, and that gain would
be extremely useful.

~~~
Thaxll
"Rewriting anything takes a lot of time and causes lots of bugs, going from
Python to less expressive language means an increased maintenance burden. But
if for some reason, you still want to rewrite it, please don’t pick Go. Rust
is better in pretty much every way."

I mean it's pretty much 'never use Go'

~~~
kbenson
Yeah, the article boils down to "If you feel the need to rewrite your python
in a statically typed language, and it's between Rust and Go, don't use Go,
because the cost of implementing in Rust is roughly equivalent, but the
performance gains are much higher."

I think with all the evidence presented, that's an eminently defensible
position. Get twice the performance of Go (in this case, obviously it will be
very dependent on project) for not much more effort, when you've already made
the choice to rewrite for performance.

------
tombert
I'm curious what the performance would be if it were ported to Haskell or
OCaml. It almost certainly would be slower than Rust, but I think some
benchmarks would definitely be interesting when comparing those languages to
Go.

------
mmargerum
Really interesting write up. I'd love for you to port to swift and gives us
your thoughts! :)

------
alexnewman
Plenty of ways to .orthat for options. The author should checkout the option
type in detail

