
Immutable.rs: immutable data structures for Rust - tosh
http://immutable.rs
======
elihu
> Most of these data structures support in-place copy-on-write mutation, which
> means that if you're the sole user of a data structure, you can update it in
> place with a huge performance benefit (about an order of magnitude faster
> than immutable operations, almost as fast as std::collections's mutable data
> structures). > > Thanks to Arc's reference counting, we are able to
> determine whether a node in a data structure is being shared with other data
> structures, or whether it's safe to mutate it in place. When it's shared,
> we'll automatically make a copy of the node before modifying it, thus
> preserving the usual guarantees you get from using an immutable data
> structure.

That's a really interesting approach. I could see that being really useful in
a lot of cases, and it lets you get good single-threaded performance on code
that would also continue to work in a multi-threaded case.

~~~
Manishearth
Arc::make_mut is really nice and is something we use a bunch in Stylo to
safely mutate possibly-shared styles. Across threads.

~~~
seeekr
But isn't it an anti-pattern to wrap something in an `Arc` when some of your
use cases for it are purely single-threaded, and as such you're wasting a
certain (potentially very significant) amount of performance on upholding
memory visibility and such even when it's not needed? I have only dabbled in
Rust, so I may be misunderstanding the implications of Arc and how it is
implemented, and how it behaves in the absence of multiple threads. To me it
feels a bit like some of the relatively early Java days when even the Standard
Library was using `synchronized` (which is of course way worse than `Arc`) in
lots of places like StringBuffer (used for String concatenation), just to be
safe in case anyone ever wanted to use that code in a multi-threaded context.
Only later folks began to realize that maybe that's not such a great idea and
that you need to be able to not pay any synchronization costs when you know
you're not going to be sharing a data structure across threads.

~~~
masklinn
These collections are of somewhat limited use outside of concurrent scenarios,
and I don't think you can (currently?) be generic over allocation in Rust.
Since these structures need some sort of automatic memory management for
structural sharing, you need to use a GC of some sort, having both atomic and
non-atomic Rc would require duplicating the entire thing, once with Rc nodes
and one with Arc node, for more or less no value.

> I may be misunderstanding the implications of Arc and how it is implemented,
> and how it behaves in the absence of multiple threads

All Arc means is that refcounting operations use atomic increment/decrement
instead of regular ones.

~~~
seeekr
So that means that the synchronization cost would only be paid when making a
new copy of the Arc and releasing each of those. Ok, that extends the set of
use cases where it's acceptable to use Arc then. Thanks for clarifying.

EDIT: wording

~~~
masklinn
> So that means that the synchronization cost would only be paid when making a
> new copy of the Arc and releasing each of those.

Yes. An interesting note here is that due to Rust's ubiquitous use of
references there will be little extraneous refcounting traffic unlike systems
like e.g. Python where `a = b` might trigger a refcount increase. I think it
was Armin Ronacher (mitsuhiko) who made that observation a few weeks back,
possibly on twitter?

------
winstonewert
What kind of uses cases do people have for this? Since rust already pretty
tightly controls mutability, my normal reasons for using immutable data
structures don't apply there.

~~~
Groxx
> _Why Immutable Data Structures_

> _... Rust, being what it is, does a good job of discouraging this kind of
> behaviour, and keeping it strictly controlled when it 's necessary, but the
> standard library doesn't provide collection data structures which are
> optimised for immutable operations. This means, for instance, that if you
> want to add an item to a Vec without modifying it in place, you first need
> to clone the whole thing before making your change._

> _Data structures exist which are designed to be able to make these copies
> much cheaper, usually by sharing structure between them, which, because this
> structure is also immutable, is both cheap and safe. ..._

Seems pretty straightforward. You'll still have some benefits to immutable-
data coding-patterns because it requires only read access on a value, not
write, all the way up to the moment you want to swap values (if ever). Making
that faster is a worthwhile goal.

~~~
winstonewert
Ok, but I repeat: what is the use case?

Yes, this allows me to do things like add an item to a Vec without modifying
it in place, but why would I want to do that? What's the situation where
that's helpful?

In other languages I do that, specifically to avoid mutability hell, but that
doesn't apply to rust.

EDIT: Thanks to the responders for the great examples!

~~~
Groxx
No ability to mutate -> no mutable ownership complications. Ever. And that's
probably the biggest pain-point for people using Rust.

Not _necessary_ , but can simplify things, by encouraging(/forcing) simpler
use of data by default.

~~~
cztomsik
But you get another pain-point in return - having to replace the whole state
at root position, which in turn leads to redux.

~~~
Groxx
That's a pretty normal way to handle immutable-data systems, yes.

------
bjoli
I you are into immutable and c++ you should really have a look at Immer by
Arximboldi (who is a user here on HN). Really amazing stuff:
[https://github.com/arximboldi/immer](https://github.com/arximboldi/immer)

~~~
m0meni
The other immer[0] of note is a JS library that gives you immutability with a
mutable API i.e. you mutate your code as normal, but all changes are proxied
to a copy, which is then returned as a result of your mutations.

[0]:
[https://github.com/mweststrate/immer](https://github.com/mweststrate/immer)

~~~
cryptonector
Sounds a lot like jq's jv API:
[https://github.com/stedolan/jq/wiki/C-API:-jv](https://github.com/stedolan/jq/wiki/C-API:-jv)

------
muizelaar
[http://smallcultfollowing.com/babysteps/blog/2018/02/01/in-r...](http://smallcultfollowing.com/babysteps/blog/2018/02/01/in-
rust-ordinary-vectors-are-values/) is an interesting look at how Rust's
ownership system lets you treat the standard collections as values.

~~~
slaydemons
Good read, if you are the author I am not sure if feedly is lying that you
have rss but despite saying you do I can not subscribe.

~~~
steveklabnik
The author doesn’t post to HN that I’m aware of, I’ll ping him. Thanks!

------
the_mitsuhiko
The ergonomics of using immutable structures with Rust are really good. You
wouldn't know you have an immutable structure in front of you (other than that
maybe there are more Arcs than you would expect).

------
floatboth
Yay, it's no longer LGPL'd: [https://github.com/bodil/im-
rs/commit/32f58e914c84a6e965eea8...](https://github.com/bodil/im-
rs/commit/32f58e914c84a6e965eea8e732a81cc3cd160fbb) :)

------
noahdesu
I'm trying to figure out what the basis is for the implementation of the
sorted map. For example, in related project
[https://github.com/orium/rpds](https://github.com/orium/rpds) they explicitly
note that they are basing their sorted map on Okasaki red-black tree.

~~~
tom_mellior
ordmap.rs notes that it's based on B-trees. Doesn't that help?

------
cryptonector
This could really use some examples.

I hope this is as easy to use as, say, the jv functions in libjq [0].

[0]
[https://github.com/stedolan/jq/blob/master/src/jv.h](https://github.com/stedolan/jq/blob/master/src/jv.h)

------
nurettin
This looks more like a view library where you have an iterable and views that
let you access them in convenient ways and copy as little as possible than a
data structure library where data is created on the fly within the data
structure as needed.

