
You can't Rust that - stablemap
http://lucumr.pocoo.org/2018/3/31/you-cant-rust-that/
======
pcwalton
I really like the way you captured one of the fundamental differences between
Rust and C++ as "Things Move". That's an interesting way to summarize it that
I hadn't really considered before—and I designed a lot of that system :)

~~~
jnordwick
Is is even remotely true though? C++ probably moves things around more than
rust, and I thought rust would want to reduce cache churn. It isn't like GC
were things magically change locations.

I'm not really sure I understand what he's getting at with that description.

~~~
saagarjha
> C++ probably moves things around more than rust

I don't think so, actually. As far as I'm aware moves are only done when
explicitly asked for, because any implicit move would have the consequence of
breaking references and being unsafe in general if you're not careful.

~~~
wilun
> As far as I'm aware moves are only done when explicitly asked for

Certainly not. Move are attempted (in the sense that the move
constructor/operator= will be called if it exists) from rvalue references.
Those are for example all temporaries. That has let accelerate all C++
programs using std objects by simply recompiling them (with the new versions
of the objects supporting the move while it did not exist in the old versions)

~~~
saagarjha
Well, it makes sense to move temporaries. There's no point in copying them
again if the original won't be used.

------
jnordwick
I don't like the Things Move example. I'm not sure how true the general
statement is (I'd never thought of it that way, but it isn't like how GC moves
things around, and I'm not sure things are even more than in C++ -- I thought
rust reduced unnecessary moves because would kill cache performance), but the
example isn't entirely correct from my perspective.

Return values that fit into a register will be returned in a register, and his
example is an 8 byte struct, so that returns in a register. Return values
larger than a register will add an implicit first argument that is a pointer
to memory where the return value should be written to. In that sense, it is
very similar to C++ in that you are initializing into an allocated buffer.

As for "Refcounts are not Dirty", I would greatly disagree. Using refcounts to
get around an overly aggressive borrow checker seems to be an ugly developing
pattern in rust, and I feel they are giving away the performance many are
fighting for by adding all these little inefficiencies to idiomatic rust. Add
some refcounting here, add a Box or other indirection there, a chained monadic
interface that can't short circuit and has to continually do error/null
checks, etc... Soon it is death by a thousand papercuts. People fight hard for
that extra 5% in performance only to have it taken away from them in interface
and language issues.

Edit: Forgot about handles. Ugh. Completely unacceptable when you want to grow
your tree data structure and you have to do a realloc and basically copy every
node. If your tree is complete, then you are copying the whole tree every time
you start a new level. The conclusions sound more like ugly hacks, than what
you would properly design.

~~~
the_mitsuhiko
If a value returned from a function actually moves or not is currently up for
Rust to optimize. It's not something you can depend on.

About the refcounts: since the counting is explicit (calls to clone()) they at
least in my experience don't really show up. Most of the refcounted objects I
deal with bump the refcounts once when some task spawns and decrements it when
it ends. I have yet to see refcounts to change in hot code paths.

//EDIT for your edit:

> Edit: Forgot about handles. Ugh. Completely unacceptable when you want to
> grow your tree data structure and you have to do a realloc and basically
> copy every node.

Sure, but that's not my point anyways. At any point you can fall down to
writing unsafe code and building a safe abstraction on top of it. This is to
help developers not run into walls. I don't think that handles are the best
thing invented but I don't think "well you can't do that in Rust until we some
time in the future" and not provide an alternative is a particularly good
suggestion.

~~~
berkut
Are Rust ref-counts atomic? (I'd assume so).

Atomic ref-counts can actually have a surprising amount of overhead in many
multithreaded scenarios, so much so that going back to raw pointers over
shared pointers in C++ for some use cases of passing pointers around can
provide a huge win.

~~~
therein
There is Rc and Arc, the latter being atomic. If you want to pass them between
threads, you'll want an Arc, and in fact you will want an Arc<Mutex<Data>> so
your data becomes:

    
    
      let data = Arc::new(Mutex::new(Data::new()));
    

And if you want a mutable static global that's accessed across threads, you'll
need something like:

    
    
      lazy_static! {
        // 0 -> None
        // 1 -> Circles
        // 2 -> Bezier
        static ref DRAW_ON_TOUCH: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
      }
    

I love Rust but it is making easy things harder than they need to be.

~~~
gmueckl
Hm, this distinction between non-atomic and atomic ref counting sounds like a
scat-gun sized footgun waiting to go off when it is least expected. Is there
any protection for non-atomic ref counting in a multithreaded environment in
Rust?

~~~
pcwalton
> Is there any protection for non-atomic ref counting in a multithreaded
> environment in Rust?

Of course. Rust is memory safe. The compiler will prevent you from sharing
non-thread-safe reference counted objects between threads.

This is a significant advantage over C++, where the compiler offers no such
protection and so the typical solution is just to use atomic reference
counting (shared_ptr) everywhere.

~~~
gmueckl
So are you claiming that the exact same solution is good in rust and bad in
C++?

~~~
the_mitsuhiko
I'm not the person you are replying to but I would never use a non atomic
refcounted smart pointer in C++ because the risks associated with it are just
too high. In Rust however the compiler protects me from misuse.

Separately emotionally I feel different about Arc<T> because all the counting
is explicit and I see it. Understanding where shared_ptr<T> in C++ counts
however can be quite a challenge, even if you consider yourself proficient
with C++.

~~~
gmueckl
I may be in the minority here, but if you are programming properly, your
understanding of the code should always be good enough that this is a non-
issue. If you manage to trigger a compiler error like this at all, you are
most likely hacking, not developing.

~~~
empath75
Real programmers apparently never make mistakes.

~~~
gmueckl
No. Real developers design their data structures and algorithms carefully.
When it is tine to implement them, everything should be laid out clearly
enough. There are industries where this kind of process is required and it
truely shows in the quality of the results.

~~~
kbenson
If the vast majority of software is not made by real developers (and I think
by the definitions you are espousing it is not), then I think one of the
definitions is in need to change.

As beneficial to your argument as it would be for that change to be the
definition of "software", I don't think that's likely.

~~~
gmueckl
I needed to make a distinction in the dedinition I gave. And I stand by it,
even if the wording comes across as snarky. There are software engineering
methods that are proven to work. They do not involve just diving in and
changing the code. But I also think that there are valid reasons to not apply
these rigid development practices to certain kinds of projects. For the rest,
we might well get mandatory development standards forced on us to

I am well aware of the pressure on developers to get requested feature done
quickly. But the vast majority of bugs that get shipped would be easily
prevented by more thorough processes. All those projects writing highly
reliable software demonstrate this. But the price tag has a few zeroes
appended as a result.

~~~
pohl
_There are software engineering methods that are proven to work._

Rust’s type system is arguably one of them.

~~~
gmueckl
I was talking about processes, not tools. Rust is just tool. But good software
comes from thorough processes.

~~~
kibwen
Good software comes from processes that aren't afraid to adopt tools to
automatically enforce those processes in order to guard against human
oversight.

~~~
gmueckl
Tools come from processes. You need to know what the tool needs to do (the
process) before you can write it.

------
z3t4
I'm not a real programmer (as in someone who do not write low level code), but
do real programmers actually rely on pointers - knowing that the data might
move or change !? (I program in JavaScript where all values are immutable)

~~~
a_humean
edit: don't vote the guy/gal down just because they are admitting some
ignorance and seeking clarity

I would quickly disabuse yourself of the notion that all values are immutable
in JavaScript, as otherwise you will cause yourself and colleagues a lot of
pain in future. As someone that has to write or maintain a lot of JavaScript,
saying that I don't have to think about data changing over the course of a
program doesn't strike me as true at all (I wish it was).

[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Data...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Data_structures)

As linked, only some of the primitive values in javascript are immutable. So
while you can be confident in this:

const x = 'a perfectly well formed string';

// lots of code in between

console.log(x) // will always print 'a perfectly well formed string'

You cannot be confident in this at all:

const x = {

    
    
      a: 'a perfectly well formed string within an object'
    

};

// lots of code in between

console.log(x.a) // absolutely no guarantee that x.a will point at the same
string as at the time x was initialized as an object, or that the key will
even exist (will fallback to the type 'undefined' if you attempt to read it).

JavaScript has references and values like most languages, but you aren't
dealing with memory as explicitly as Rust or C. The reason is in large part
because memory is garbage collected in Javascript.

~~~
catnaroek
That's because the real value is the object identity, not its state. Trust me,
object identities are immutable.

~~~
eslaught
That's like saying the pointer 0x123456 is immutable. Which is strictly
speaking true, even in a language as lax as C. But it's clearly not what
people mean when they talk about pointers (or references) being mutable. What
people mean is that either variables are mutable, or memory is. So if you say:

    
    
        // suppose initially x == 0x123456 and x has type char *
        x++
        // x == 0x123457
    

this is really changing the value of a variable (neither of the pointers
0x123456 or 0x123457 have been changed in any way).

Or if you say:

    
    
        *x = 'a'
    

now you're modifying the memory at the address 0x123457 (again, the pointer
itself has not changed).

The fact that the pointer itself, as in the address to memory (as opposed to
the variable holding that address or the contents of the memory being referred
to) is immutable is of approximately no value to anyone.

And frankly, it makes no difference (for these examples) whether this is a
language with pointers or references. You can still set variables holding
references to new values (if the language allows mutable variables), and you
can still mutate the memory that references address (if the language allows
mutation of the objects referred to by references), and that's plenty of rope
to hang yourself on even without being able to do pointer arithmetic on the
address values.

~~~
catnaroek
> But it's clearly not what people mean when they talk about pointers (or
> references) being mutable.

The semantics of a programming language is what it is, not what you want it to
be, unless you explicitly choose a language that allows you to express exactly
what you mean.

~~~
tuukkah
The semantics of a programming language isn't determined by how you talk about
it or even how the language specification talks about it.

The original statement was: "(I program in JavaScript where all values are
immutable)" Its meaning depends on the semantics of 'values' and 'immutable'.
If by 'values' you only mean primitive values (excluding objects and arrays)
or by immutable you only mean immutable identity (excluding mutable state),
you're comparing apples to oranges instead of Rust to JavaScript.

Immutable.js and PureScript exists for a reason: in JavaScript, not all values
are immutable.

~~~
catnaroek
> in JavaScript, not all values are immutable.

 _Object states_ are mutable, of course. But they are not values. You can't
bind object states to variables. If you use objects as proxies for values that
exist in your head but not in in JavaScript's semantics, that's fine, so long
as you keep in mind the distinction between what you wish you were using and
what you are actually using.

------
skybrian
As a non-Rust programmer, I'm finding the memory-mapped data example to be
very opaque. Does anyone care to explain it?

~~~
grayrest
It's a contrived example and doesn't make a whole lot of sense aside from
demonstrating what he means by handle. I'm not an expert but I'll have a go at
explaining it.

Start at the `Data` struct. It contains a Copy on Write (`CoW`) reference to a
vector of bytes (`u8`) with a lifetime labeled `'a`. This is the Handle for
the data. You get one by calling `Data::new` and passing in something that can
be converted to the CoW.

The example is hard coded to work with a vector of u32s (driven by the
`Slice<u32>` in `Header`). To use it, you'd call `get_target` with an index
and get a u32 back. The other methods on data are doing the pointer math
(offset) and casting (`transmute`, `from_raw_parts`) the byte array into a
slice of u32s in a safe way.

I don't see anything verifying that the byte array passed in is, in fact, a
bunch of u32s so I assume that's a given.

~~~
the_mitsuhiko
It’s actually a simplifiedversion of what we do. We deal with debug
information files and write custom cache files thag look similar to that.

[https://github.com/getsentry/symbolic/tree/master/symcache/s...](https://github.com/getsentry/symbolic/tree/master/symcache/src)

------
joeconway
Thank you Armin. Your rust work for sentry has been a great primer in the
language for me.

------
glenjamin
The semantics of the final example sounds a lot like the concept of an Atom in
clojure -
[https://clojure.org/reference/atoms](https://clojure.org/reference/atoms)

Is this swap/deref pattern something that can or should be wrapped up into a
create?

------
mwcampbell
Given the "things move" point, would it be feasible to use a compacting memory
manager with Rust, e.g. for memory-constrained applications?

~~~
kibwen
Rust allows you to take interior pointers to things (important for
performance), which precludes the ability to move objects within memory at
random. But for "memory-constrained" applications like embedded
devices/microcontrollers, fragmentation isn't a problem in the first place
because you often don't have a heap. For long-running programs that do have
heaps, picking a modern memory allocator (jemalloc, tcmalloc, et al) will go a
long way towards reducing fragmentation. And if you really need compaction,
you could probably design a Rust library to provide it for certain types
(though the operations it could provide would likely be restricted).

------
LifeLiverTransp
[https://www.viva64.com/en/b/0324/](https://www.viva64.com/en/b/0324/)

