
Thoughts on Rust, a few thousand lines in - airstrike
https://rcoh.me/posts/thoughts-on-rust-a-few-thousand-lines-in/
======
ilovecaching
I'm a network engineer, and I've done C++ for over a decade now. One of the
nice things about Rust is that they decided to go with async over fibers,
which is in line with how most high performance C++ is written. The Rust team
also isn't rushing Future out the door, so it's coming along much nicer than
the C++ Future, which is usually replaced because it's not monadic.

Rust is great, and I highly recommend learning Rust if you want to become a
better C++ developer. C++ introduces a lot of _unnecessary_ headaches Rust
completely solves. Rust is worth the switch if only because reading error
messages from expanded templates is horrible compared to type system that
actually understands type parameters.

I'm really bullish on Rust becoming even more mainstream, to the point anyone
can pick it up and use it in their business without a second thought by 2020.

~~~
skyde
I am a big fan of rust but I don’t think the Async using the ”await” keyword
or using Future/promis is the way to go.

I wrote a large web app in scala using Futur only and it turned into a monster
because of it. Even if you don’t use callback you still need to manualy unwrap
them and they pollute your méthode signature.

Then more recently I wrote a pub/sub server in c# using the “await” keyword.
While much better it still polute your method signature and make the debugger
and stacktrace useless. Also you still have to think about trying to no make
anything that could block spinlock or calling api that are not Async ex:
Createfile

Compare this to Golang where project like Groupcache (1) have super clean code
that replaced a thousand line of code C++ system at google.

Why do you think Golang fiber/gorotine are worse ?

(1)
[https://github.com/golang/groupcache](https://github.com/golang/groupcache)

~~~
ilovecaching
The answer to this question is quite involved. As you may already know, Rust
at one time had a fiber model, but it was removed in favor of Futures.

One big advantage of Futures is they are decoupled from their execution
environment.

For example, different types of executors can be used depending on the type of
Future. For example, you may have an IOThreadPoolExecutor to handle accepting
and responding to connections that should spend very little time blocking and
a CPUThreadPoolExecutor for offloading heavy processing. In Go, you have no
say in how goroutines are scheduled, you sacrifice control to reduce
complexity.

Another big one in network programming is the polling mechanism. In Go, you
have no control over your polling mechanism, whereas with a Future is
decoupled from the event loop, and you can write your own event loop on any
experimental kernel APIs you'd like.

Rust made the right move because in Rust, just as in C++, we want maximum
control. Futures are more primitive, and it's possible to build coroutine
models on top of them, but not vice versa.

~~~
skyde
I agree with you. I guess the best solution would be something in between
“await” and goroutine.

My understanding is the main issue with “await” model is that you only
abstract The Task model.

Running task don’t gave a real ID and stack like “Go” and “Erlang” you can
list all running fiber how much memory and CPU they use ...

I believe you could write your own event loop easily in Go if they exposed the
internal api used to pause and resume goroutine, you can already pause
goroutine by having them wait on a mutex and resume them from your custom
event loop by releasing that mutex.

------
throw0u1t
One thing I don't like about Rust is how taking a slice of a string can cause
a runtime panic if the start or end of the slice ends up intersecting a multi-
byte UTF-8 char.

I would prefer it if this feature didn't exist at all rather than cause
runtime panics.

[https://play.rust-
lang.org/?gist=e02ce5e9aacfee3a2b4917d5624...](https://play.rust-
lang.org/?gist=e02ce5e9aacfee3a2b4917d5624b9ec1&version=stable)

~~~
jzelinskie
Go indexes bytes on strings, even though there's a builtin type called Rune
which delimits utf-8 codepoints. This is yet another footgun. Is there a
language that doesn't handle this poorly?

[https://play.golang.org/p/CkBp0w8T621](https://play.golang.org/p/CkBp0w8T621)

~~~
Skunkleton
UTF-8 is at odds with efficient array indexing. I like pythons approach where
bytes and strings are distinct types, though I have no idea what it is doing
under the hood.

~~~
colatkinson
I actually had to work with Python strings at the C level recently, and their
approach is pretty clever. IIRC, the runtime can take any common form of
Unicode, and will store it. When you access that string, the accessor requests
a specific encoding, and the runtime will convert if need be, and then store
it in the string object.

So it handles the (very) common case of needing the same encoding multiple
times (e.g. for all file paths on Windows), while not introducing too much
overhead in memory or speed.

I could be mistaken on exact details though, especially since I recall there
being multiple implementations even within py3.x.

~~~
Skunkleton
Any idea how it handles indexing? Does it convert everything to 32 bit chars
and ignore graphemes?

------
KenanSulayman
> A lot of great, well loved, crates don’t have a lot of Github stars.

This was a really important learning for me. When I'm looking for crates to
solve a problem and there's only a handful of them, I almost always go through
every single one of them, even if some have thousands and the others only tens
of downloads.

It's an incredible feeling to find those unknown diamonds..

~~~
siscia
Make sure to thanks the maintenars of those repo. It really help!

------
shepmaster
> the escape hatch suggested on the internet,
> `partial_cmp(...).unwrap_or(Ordering::Less)`

This is often a Bad Idea, as you get unstable sorts and you are right back to
the same problem.

\- Explanation: How do I get the minimum or maximum value of an iterator
containing floating point numbers? —
[https://stackoverflow.com/a/50308360/155423](https://stackoverflow.com/a/50308360/155423)

\- Example:
[https://play.integer32.com/?version=stable&mode=debug&editio...](https://play.integer32.com/?version=stable&mode=debug&edition=2018&gist=d459a0de3fc4dce15a090d078cf8f15e)

See also:

\- How to do a binary search on a Vec of floats? —
[https://stackoverflow.com/q/28247990/155423](https://stackoverflow.com/q/28247990/155423)

Instead, use a wrapper type or raise a panic.

~~~
stabbles
Isn't there a default _total_ order for floats? E.g. -Inf < -1.0 < -0.0 < 0.0
< 1.0 < Inf < NaN?

~~~
qznc
Yes, there is: [https://en.wikipedia.org/wiki/IEEE_754#Total-
ordering_predic...](https://en.wikipedia.org/wiki/IEEE_754#Total-
ordering_predicate)

It is not something to use by default though. It is implemented in software
and thus a lot slower than the hardware comparison.

~~~
dkarl
I'm curious as to why it isn't implemented in hardware. Is it really so rare
to need to sort floats, or so common to need a different ordering when you do?

~~~
stabbles
Of course sorting floats happens a lot. In practice one rarely encounters
NaN's and ±Inf's, so fast comparison for concrete values is the default. I
don't know why the 'slow' total order is not implemented in hardware though.

But fortunately in comparison sort algorithms that run in O(n lg n) you can
get away with doing an O(n) partitioning of the array into [-, +, NaN] and
then applying a fast integer comparison operator to the negative values (-)
and positive values (+).

In fact the above idea ties in neatly with QuickSort, which is already based
on partitioning & sorting recursively.

~~~
bsder
> Of course sorting floats happens a lot.

Is this true?

I am actually struggling to remember the last time I did a sort with a
float/double as the key--especially in a performance bounded context

... <thinking> ...

Aha. Graphic engine. Octtree with coordinates.

I really had to think about that.

So, I'm a bit skeptical of float sorting happening a "lot".

Is this perhaps an ML primitive somewhere?

~~~
harrisi
Languages that only have floats, such as JavaScript and Lua, certainly sort
floats quite often.

------
_hardwaregeek
Rust is one of the few times where the language/ecosystem does precisely what
I wished it would do. For instance, being able to partially destructure JSON
into a struct in a typesafe manner is just awesome.

~~~
EwanToo
This sounds really useful, you don't happen to have an example of this do you?

Thanks in advance!

~~~
_hardwaregeek
I’m basically talking about Serde. Read the section titled “Parsing JSON as
strongly typed data structures”. What’s nice is that you don’t have to include
all the fields in the JSON in the struct. Serde will only give you the ones
defined by the struct.

[https://github.com/serde-
rs/json/blob/master/README.md](https://github.com/serde-
rs/json/blob/master/README.md)

~~~
hu3
I'm not sure I'd consider that lib any safer than the average serialization
library when things like this happen: [https://github.com/serde-
rs/json/issues/464](https://github.com/serde-rs/json/issues/464)

~~~
steveklabnik
Panics are memory safe, so that’s still more safe than many parsing bugs.

~~~
hu3
The issue does not mention memory safety and neither did I. Honestly, knee-
jerk reactions like "BUT MUH MEMORY SAFETY" doesn't inspire confidence
specially when it couldn't help saving that dev from the troubles and bugs
documented in the issue. To quote a few:

> it was a hassle to track down because Rust itself didn't complain and the
> panic message during serialization wouldn't tell me which file of the
> hundreds of thousands was causing it to die. For lack of a purpose-built
> tool, I had to manually bisect it until I narrowed it down.

> That said, definitely a footgun in the standard library to be remedied.

> My main concern here is getting rid of the footgun if at all possible. I
> really don't want to have to maintain a special "Never allow these types to
> creep into structs I'm deriving Serialize/Deserialize on, because the
> compiler certainly won't warn you" audit list.

If that's considered safe in Rust's standards then I rest my case.

~~~
oconnor663
Safe in rust usually has a very specific meaning: [https://doc.rust-
lang.org/nomicon/what-unsafe-does.html](https://doc.rust-
lang.org/nomicon/what-unsafe-does.html)

------
vmarsy
One thing Rust doesn't seem to be doing very well yet is guard clauses,
specifically when handling Option<T>.

I've seen and appreciated the use of guard clauses in many languages, as a
good way to quickly check for a few conditions at the top of a function, and
return early if those conditions aren't met.

Since it seems that Option<T> are recommended in Rust, there's a lot of time
you want to quickly return if `Some(x)` is _not_ here (i.e. it's `None`), and
if it's here, continue through the function, without having an unnecessary
indentation from an extra brackets.

There seem to be a good amount of smart discussion into handling those [1][2].
some threads are more than a year old, but it seems to be making progress.

[1] [https://github.com/rust-lang/rust/issues/45978](https://github.com/rust-
lang/rust/issues/45978) [2] [https://internals.rust-lang.org/t/pre-rfc-allow-
pattern-matc...](https://internals.rust-lang.org/t/pre-rfc-allow-pattern-
matching-after-guard-clause/6238)

~~~
steveklabnik
You can use ? on an Option if your type returns an Option. If it returns a
Result, you can use ok_or()?, and at some point in the nearish future, you can
just use ?.

~~~
vmarsy
I see, not very familiar with both those idioms `?` and `ok_or()?`

My current understanding is that those would return an Error only? I was more
describing cases where you do want to return, but not necessarily return an
`Error`.

For instance in a simplified example function that returns a boolean, you
could decide to return `false`. is it possible there?

    
    
        // Function that returns a boolean value
        fn is_equal_to_ten(n: Option<u32>) -> bool {
            // some one liner  that checks for None, if it's not none, gives you `x` when `n` matches content of `Some(x)` (not real code): 
            if let Some(x) = n else { return false; /* what to do in case it's a None*/ } 
            
            // `x` is available here:
            return (x == 10);        
        }
    
    

Would this be considered bad practice in Rust?

~~~
steveklabnik
The question mark operator works via a trait, Try. Both Option and Result
implement Try. If you use ? on a None value, it will return None, just like
using ? on an Err returns an Err.

ok_or is a method on Option that would let you manually convert it to a
Result. You could then combine it with ?, turning a None into a specific Err.

It won’t help for stuff that returns bool, it’s true.

~~~
vmarsy
Thanks for the detailed answer :)

------
cryptonector
> Unlike nearly every language I’ve ever used, Rust actually encourages
> variable shadowing.

Haskell does it too, for the same reason/purpose / with the same effect.

~~~
favorited
Swift does, but typically for Optional promotion. You see stuff like `guard
let delegate = delegate else { return }` inside functions all the time,
shadowing a property with a local & promoting the type from Optional<T> to T.

It's not the same as the Rust example because you're shadowing an ivar to a
local, but since `self.` is implicit you're still shadowing.

~~~
shepmaster
> for Optional promotion

You'll see the same in Rust

    
    
        fn example(name: Option<String>) -> Option<usize> {
            let name = name?;
            Some(name.len())
        }
    

Or

    
    
        fn example(name: Option<String>) {
            if let Some(name) = name {
                println!("{}", name.len());
            }
        }
    

A main difference is the requirement to use `Some`, which allows for the
flexibility to apply to any enum.

> but since `self.` is implicit

To make sure I'm following, do you mean that Rust's `self.` is implicit in
Swift?

~~~
favorited
That all makes sense. I've only done a couple days of Rust, so my recollection
is spotty. The shadowing makes sense because if a local variable was moved
out, then you're not really shadowing it anymore. Is my understanding of that
correct?

> do you mean that Rust's `self.` is implicit in Swift?

 _Swift 's_ `self.` is implicit in Swift – in most contexts, to access a
property the `self.` is not required. `self.name = "John"` and `name = "John"`
are equivalent (assuming self is an object with a name property).

There are places where explicit `self.` is required though – when you want to
differentiate between a shadowed local and a property (obviously), or when
you're inside a closure (to make it clear that the closure is capturing self,
not just capturing a reference to the property).

~~~
shepmaster
> The shadowing makes sense because if a local variable was moved out, then
> you're not really shadowing it anymore. Is my understanding of that correct?

This is kind of a philosophical corner: can you shadow something that isn't
there anymore? Once you've moved out of something, if you attempt to use the
old name, then you'll get a compiler error different from "no such variable",
so it's still there in _some_ sense.

Pragmatically, I think you are on the money.

------
dkarl

        let foo = "...";
        let foo = parse(foo);
        let foo = escaped(foo);
        ...
        doSomethingWith(foo);
    

I don't see how this is helpful for avoiding the bug described. The most
common bug with this type of code is mistaking which form "foo" represents at
a given line of code, or that form changing as the code evolves. For example,
if one programmer writes

    
    
        let foo = "...";
        let foo = parse(foo);
        ...
        doBarWithFoo(foo);
    

and another programmer comes along, doesn't notice the call to doBarWithFoo,
and needs an escaped version of foo:

    
    
        let foo = "...";
        let foo = parse(foo);
        ...
        let foo = escaped(foo);
        doBazWithFoo(foo);
        ...
        doBarWithFoo(foo); // This still looks correct in isolation
    

This is a classic problem with mutable variables that frequently causes hard-
to-spot bugs. Whereas if each form has a distinct meaningful name, this change
wouldn't introduce a bug, and if somehow a bug were introduced, good names
will make it possible to spot even considering the line in isolation:

    
    
        doBarWithFoo(fooEscaped);  // Hey!  The input to doBarWithFoo shouldn't be escaped!
    

If the only useful form of foo is the final one, then you can avoid having
accessible names for the invalid intermediate forms like this:

    
    
        let foo = escaped(parsed("..."));
    

Or like this for more complicated logic (I'm not a Rust programmer (yet) so
this may not be the right syntax):

    
    
        let foo = {
            // Complicated logic
            finalForm;
        };
    

I feel like if I ever write a lot of code in Rust I'll find a linter rule that
warns about shadowing variables and use it religiously. But maybe I'm missing
a use case where it's crucial.

~~~
Misdicorl
I believe the way this is solved is by having the type of escaped(foo)
different than that of parse(foo) and only accepting a EscapedString in doBaz
and an ParsedString in doBar.

Your type structure at no extra runtime cost is String : ParsedString :
EscapedString.

This ensures you don't escape strings before parsing them too. Nice!

~~~
afraca
This is common in Haskell as well using `newtype`. You can have type aliases
like

    
    
      type A = B
    

but you can use the following function with a B:

    
    
      f :: A -> _
    

whereas

    
    
      newtype EscapedString = EscapedString String
      f' :: EscapedString -> _
    

would prevent from using f' with the wrong data. Newtype is a zero-cost
abstraction.

------
cphoover
I've not done any rust dev before but the whole variable shadowing thing
really scares me, in that it makes it not obvious where the initial
declaration comes from. This seems difficult to reason about.

~~~
int_19h
IMO, it should scare you less than mutating the variable, which is the
equivalent pattern in most other languages.

------
StreamBright

      let foo = "...";
      let foo = parse(foo);
      let foo = escaped(foo);
    

Is it really shadowing or mutation of foo?

I would consider this shadowing (something you can do in OcaML):

    
    
      let foo = "..." in
        let foo = parse(foo) in
         let foo = escaped(foo) in
            dosomething(foo);;

~~~
UK-Al05
You can't mutate variables in rust without declaring mut

~~~
StreamBright
But how do you know just by reading that code? It would not be obvious to me
at all.

~~~
empath75
let foo = bar; let foo = bat;

is shadowing

let foo = bar; foo = bat;

is a compilation-time error because foo isn't mutable.

let mut foo = bar; foo = bat;

is reassignment.

in working code, either foo is declared as mutable or it's not, and it's
pretty obvious from the code what's happening.

~~~
StreamBright
I guess. I would just never write code like that.

------
kazinator
> _Like Go, Rust can compile statically linked linux binaries._

The GNU C library (needed not only by C programs for C things) doesn't support
static linking, so the only way this is possible is to use another library
entirely, or raw inlined syscalls (where applicable).

~~~
steveklabnik
Yes, we have full support for MUSL. It's a

    
    
      $ rustup target add x86_64-unknown-linux-musl
      $ cargo build --target x86_64-unknown-linux-musl
    

away.

~~~
ekidd
Yeah, Rust's musl support is great. We use it heavily at work for all sorts of
CLI tools. Many thanks to everybody who worked on this.

(And if you need to link against common C libraries like OpenSSL or
PostgreSQL, I maintain a Docker image with the necessary C toolchains, and
instructions on how to use it: [https://github.com/emk/rust-musl-
builder](https://github.com/emk/rust-musl-builder). There are a couple of
similar images out there, too, I think.)

------
wyldfire
Great job. Just a nit/request for that gif illustrating agrind's use -- I keep
watching it to see what args you've passed to see how it's being used but the
output finishes and loops before I understand what I'm seeing. Not sure if
it's a property of the image or my browser but if you can turn off the looping
in the image that might work better. Or add static frames at the end for the
slow folks like me ;)

------
cryptonector
This makes me want to add `p90`, `p50` and so on to jq...

~~~
rusbus
You could always just pipe JQ output to angle grinder...I should make a
output-json mode so you can pipe it back to JQ :-P

------
lincpa
I think the concept of ownership of rust should be divided into "ownership "
and "right of use " two parts.

This is more in line with the "bank lending model " it advocates.

------
bqkfbe
Wow the Rust hype is really an echo chamber right now. Seems very academic and
nobody really seems to be using it in production..

~~~
nicoburns
Quite a few people are using it in production: dropbox, npm, and I believe
amazon and microsoft.

 _I 've_ used it in production, and subjectively it was much easier to work
with than C++.

~~~
steveklabnik
Amazon and Microsoft both are, yes.

