How the Rich Hickey concept of simple vs easy is applicable to Rust?

Hi Rusteacian guys!

In the title, I'm talking about the intriguing concept discussed in this fantastic presentation:

I've been playing(quite passively) with Rust for a while, but I often feel like I'm only scratching the surface.
Today, I find Rust to be quite productive. Overall, I think the language is well-structured and cohesive in design. Once you overcome the initial learning curve, you can truly appreciate its value (I think mainly thanks to the Lsp, Cargo, Enums, Results, Traits and Iterators).

However, whenever I tackle something less straightforward and dig into the underlying model, my confidence wanes. The initially well-coherent design starts to appear less elegant, becomes more confused/convoluted,and I often end up creating rather wild smart pointer chimeras.

I began to consider whether the concept of simplicity, as advocated by Rich Hickey, applies to the language as a whole. However, it seems to become less applicable the deeper I delve into the language.

What are your opinions about that?

Please give me your througths, I'm genuinely interested in hearing about your experiences!

Do you use Rust to tackle specific challenges?
Have you been using it extensively in a professional context?
Are you more of a hobbyist, similar to my own experience?
Could it be that I'm lacking fundamental knowledge?
Is it possible that a deeper computer science background is needed to fully appreciate Rust's low-level aspects?

What is needed is an appreciation of the difficulty in accomplishing its goal to 1) not compromise performance as compared to C/C++ and 2) provide memory safety. Rust is the best solution we have to that extremely difficult problem. I think the areas where ease of use suffer are mostly due to the difficulty of this problem, although improvements are still being made.

But I think your question is also much too general. If you describe what you mean by the following, giving specific examples, then you may find that there are cleaner ways to do things. Without being specific, there is no way to know whether the issues can be solved using a different design.

4 Likes

Is there a slide with what "simple" means, for that talk? 5:00 was as close as I found in a quick scrub, but it's still not defining it, and I'm not watching the whole presentation right now. "WordA vs WordB" talks tend to be meaningless without the speaker's precise definition for what they deem the words to mean.

Go has poisoned "simple" for me in general; see https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride#simple-is-a-lie. With the classic https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/, it's hard to say whether simple is truly simple, or just ignorant of actual problems.

6 Likes

Good point.

As you mentioned, this question may seem quite generic, and I acknowledge that, as a stereotypical customer, I might not be entirely certain about what I'm asking. My experience might not be particularly valuable in this context because I'm not entirely aware of what I don't know.

I'm very interested in hearing about the experiences of real Rust users.

I'll admit that I may be somewhat detached from the high-performance domain, as Rust has fascinated me for reasons beyond just its performance.

I'm not sure if I can express my feelings adequately, but I may not possess the same level of sensitivity as someone who uses Rust right.

Reflecting on it, another question arises, for instance:

You guys, in your Rust experience, have you come across solutions to problems that required the use of smart pointers, which you found both satisfying and elegant?

Definitions are hard, again :slight_smile:

From my originally-from-C++ perspective, Box<T> and arguably even Vec<T> are "smart pointers", because they clean up after themselves. And ownership-based resource management (RAII) is absolutely the most elegant resource management solution I've ever used.

Perhaps you're asking about reference-counted smart pointers specifically? It's true that those are rarer in "elegant" code. But that's because sharing ownership of stuff is fundamentally more complicated. And remember that in many languages, that's actually the only thing available. Every object in Java, for example, has shared ownership. So one perspective is that Arc<RwLock<T>> is just exposing complication that already existed.

2 Likes

I'm guessing that you're using Rust smart pointers (Rc/Arc I assume) in the same way you've used object references in a garbage collected language like Java or Clojure. If so, then I can understand why you may have found that difficult. Again, since you haven't given any examples I can only guess.

Am I right in guessing that you're doing a data structure with pointer cycles? That's usually how you end up with "wild smart pointer chimeras."

I would describe Rust's design as built around the waterbed principle that pushing complexity down in one place, instead of making the complexity go away, often makes it go somewhere else.

It's why you notice this happening when doing stuff that—by your own judgement—is less straightforward. The complexity in your solution has to go somewhere, and Rust has a very simple runtime. If the runtime won't do that work—and that work is not simple to accomplish—you'll have to do it.

From both my own experience and secondhand from Rust, C++, and Functional Programming people, there is no such thing as an "elegant" way to implement this. Complex spiderwebs of objects with unclear owners are inherently inelegant, because maintaining an invariant over the entire graph requires subtle inductive reasoning over every function that touches the graph.

These things should be encapsulated in a module with a simpler interface, even if you're working in a memory managed language, because they're breeding grounds for bugs anyway.

9 Likes

To be perfectly honest, I think this is a general sign of the lack of experience.

Specifically when you are dealing with something not covered by the median CS course at universities or the mainstream of software engineering (eg., enterprise web application development), your architectural problems will come less and less from the language side, and much more from the domain itself.

I've been working for a small personalized medicine company for over 3 years that sports a dedicated IT development team for an in-house administrative site; I'm not part of that team. In addition to software engineering, I have a background in bioinformatics and data science, so I'm more on the science-y side in the company, helping non-coder scientists (biologists, geneticists, chemical engineers) with the automation of infrastructure and statistical methodology of their research.

I have to tell you, the majority of the dedicated IT guys (some of whom have been working for the company for 5+ years) still didn't really grasp how to design the software well, where by "well" I mean that it's usable, versatile, easy to modify, and expressive. For example, they are still designing their databases around serving the UI, rather than with the domain model in mind. It is the norm that I have to build my own versions or projections of the database before I can answer any sort of research question.

Having domain knowledge (and wanting to use it) is invaluable. There is not going to be any sort of substitute for it. Not even Rust's type system. Not even Rich "Lisp is superior" Hickey's quite frankly weird hot takes on programming. You have to know what you are doing, and this necessarily entails understanding the purpose and the users of your software. This is a much overlooked detail that I think should be considered of as high importance as good language design.

And now for the Rust part. I have written several pieces of software in Rust at this company. From small utilities (eg. a custom binary compression format) to large, multi-month projects (eg. a service that takes anamnesis information from clients and spits out a large number of automated diagnoses based on multiple decades' worth of clinical research).

One time Rust's type system saved my ass miraculously. The project requirements grew very complex, but did so gradually. The medical professionals were at first unable to mention crucial details about the structure of the data and what internal consistency constraints it had to satisfy. As we did a lot of back-and-forth, I developed the diagnostics service and they came back with errors and fuether requirements in it.

Had I not have access to a strong type system enabling me to follow best low-level coding practices, I would have been completely unable to navigate such a mess. Fortunately, at the very beginning, I was principled enough to start encoding every little detail into the type system, creating custom traits, newtypes, and helper functions for eg. checking that a particular question wasn't accidentally answered multiple times in the DB (the DB does not have such consistency checks encoded in its schema, it's severely lacking normalization in some aspects). And indeed, this came very handy when refactoring was needed; I quite honestly don't think I could have released a coherent piece of software had I started writing it in Python.

So: no, Rust is not a panacea, and not a substitute for knowing your domain. It won't make complex domains magically "simple". It will "only" help you navigate the complexity of your domain without making huge errors.

5 Likes

Yes.

Although I should say that nothing I have done with Rust, professionally or otherwise, could not have been done with almost another language, C, C++, Pascal, Ada, Javascript, Python. However given our requirements for performance, correctness, robustness and security Rust helps enormously with that. Removing so many problems we would otherwise have to worry about.

Yes. Three years ago we were looking for a new language to create a system in. For reasons I won't go into. We have had Rust in production ever since.

Despite my professional efforts I count myself a hobbyist in many things. After all the world of programming, computing and computers is enormous. There is an endless supply of interesting things to pick up from scratch and be a rank beginner at.

I have no idea what your experience is.

Could be. I don't believe any mysterious knowledge, fundamental or otherwise, is required to use Rust over and above using any other language. I created useful programs in Rust before even knowing most of the language. I suspect that if you are running into difficulty building bigger more complex things that complexity is there anyway. It has to surface somewhere, in the language itself or in the code you have to write in your language.

I'm not sure what you mean there. Rust's low level aspects are much the same as any other language. We have variables, structs, arrays, operations, expressions, functions, conditionals (if), iteration (loop, while). Could be almost any language.

The higher level things get interesting: enums, match statements, generics, the type system, etc, etc. I don't believe a deep understanding of CS is needed to appreciate such things.