How has learning and working in Rust influenced how you think about writing software?

Learning to think differently about writing software has motivated me to start and continue learning Rust. I am seeing this pay off and thought to ask about what your experience has been.

I've mostly worked in loosely typed languages. (JavaScript, shell, web frameworks). Rust has influenced me to

  • think carefully about the contracts between discrete pieces of functionality in my software.
  • think similarly about data and mutation. which you could argue is a sub-point of the above :smile:

how has learning and working in Rust influenced how you think about writing software?

1 Like

Rust has made every other language look ambiguous to me. I think in terms of ownership, even in other language. Because other languages don't enforce any notion of ownership, I can't help but feel a bit unsteady when I use them. (does this class own it's members, is this function allowed to store this reference, how do I enforce that this is only called once!).

Rust has also made me want a better type system from my languages, I hear TypeScript is nice, but I've never used it. Java's generics aren't satisfactory to me. C++'s type system seems a bit too powerful, verbose, and easy to subvert. Any language that lacks a type-system all together is just terrifying (How can I refactor anything in such a lax language!). I think functional programming languages have nicer type systems, but they have their own issues for me to work through. Like thinking without mutability in Haskell, or managing effects in vastly different ways.

The difference between Rust and Haskell seems like the difference between Java and Rust, it's a paradigm away. I'll figure it out eventually, as I did with Rust, but learning Rust has certainly made it easier to learn Haskell. With all the monadic structures that Rust has to offer (Option, Iterator, Future, etc.), and the strong preference for immutability.

Overall I find that Rust has made me think more carefully about my APIs

  • how to enforce correct usage without hindering ergonomics
  • how to prevent incorrect usage without hindering correctness
  • many subtle edge cases I would have missed in a more lax language
  • and much more

I also learned quite a bit about how the underlying machine works in learning Rust, I never had to think about that when I used Java and other similar languages, and I never really learned C++ well enough to understand how it interacted with the underlying machine.

11 Likes
  • Rust has forced me to think more about data flow in applications. The borrow checker wants application data to be tree-shaped, so I need to think twice whether the problem really requires more complicated architecture, or am I just making things tangled for no reason.

  • Rust gave me vocabulary for things that exist in informally in other languages. For example, Send and Sync exist in all languages with shared memory, but other languages tend to just call functions (not types!) "(not) thread-safe" with more ad-hoc explanation of caveats and exceptions. Things that are programming patterns in C are types in Rust.

  • I did not expect that giving a separate type to memory returned from malloc (Box) would make sense. To me memory was just memory, and I would require it to be interchangeable with other pointer types. But now I see Rust's "static ownership" as an aspect of static typing, and other languages without this distinction feel "dynamically owned (typed)" in comparison.

  • Rust has proven to me that multi-threaded programming can be practical. I've got so many scars from trying that in C that I was starting to doubt whether that would ever work.

  • Rust has enforced my belief that dependencies can be helpful and easy to use. JS/npm was to me the first that changed my mind from "dependencies are a pain and a liability" to "are easy and useful", but in JS abstractions still have a cost. With zero-cost abstractions I have no excuse.

  • I don't want to write makefile/automake/cmake/bazel/mason build script ever again. In C I've spent so much time on maintaining my artisanal snowflake build, fighting with other people's preciously crafted snowflake builds. Cargo shows this is all useless busywork that doesn't need to exist.

29 Likes

Spot on kornel.

My take on in is that all those things are things you have listed, and likely more beside, are things one has to think about eventually as programs get bigger, involve multiple developers, go muti-threaded and so on.

After all, when starting out programming with simple programs a few global variables will do. One does not need functions, structures or other such organization. Structured conditionals, loops etc are fine, nothing wrong with "goto". Who needs modules? So it was when many of us first learned to program in BASIC. So it is in the modern world, to a great extent, when people program in Javascript or C or whatever.

Of course those who make a career out of it get to learn all those organizational tactics eventually with experience, hopefully. That is why we have so many books on software design, patterns and so on. That is why we have 2000 page coding guidelines.

Rust enforces a lot of that organizational thinking from the get go. Forcing one to think about data organization and so on up front. Which is a good thing but can be tough on those that have not had to think about such things yet.

2 Likes

Rust has convinced me that it is possible to wring maximal performance (subject to thermal limits) out of current and future multi-core multilevel-cache hardware. I know of no other language that can do that with general-purpose CPU architectures.

In other respects I agree with @kornel. In particular, I find that the concept set that Rust enforces: send, sync, ownership, borrowing, reference locks, and enforcement of sharable read-only access vs. unique read-write access, leads to a clarity of design that I have not experienced before.

6 Likes

I have a confession! Rust has made me greedy for correctness.

We have a project written in C that is ~30 Kloc across more than 150 source and header files. The level of scrutiny that I need to give each of those 30K lines matches that of the lines of unsafe Rust that I have in any of my projects; two dozen, maybe? Regardless, that's four orders of magnitude fewer things to worry about. Honestly, I still don't know where to begin with attempting to prove any correctness on this project. It appears an insurmountable task.

Dynamic languages? How about 57 Kloc of Python? I might not have to worry about memory safety in those lines, but I still have to worry about data races. And even worse, I have to worry about runtime exceptions with the type system.

There's also 20 Kloc of JavaScript here, another 25 Kloc of Python over there. Rinse and repeat for hundreds of git repos... And frightening, 40 Kloc of YAML! Oh dear. "Configuration as code", as they say.

So I'm greedy about correctness, and yet I cannot prove any of the C, Python, JavaScript, or YAML is "correct" in any sense beyond existing test coverage. Which I might add, because we live in the real world, test coverage is poor. Even though some projects routinely hit 100% "test coverage", that doesn't mean they test all valid input or config permutations. Fuzz testing is only something I dream about in a production code base.

And with Rust? I get to be the best kind of lazy! The compiler proves correctness of the code, I prove correctness of the logic with tests (for some value of "coverage"), and then I get to worry about other things that actually matter. (Like trying to figure out how to prove all this other code is correct! lol)

I have another confession! Rust changed my mind about error handling.

As it turns out, handling errors is not optional. We like think that we can just ignore things like socket timeouts, or file-not-found and the like. Truth is, error handling must be enforced for them to actually be handled. And Rust does a decent job, here. It even offers an escape hatch to catch-all errors into oblivion if you can afford to be negligent. But putting error handling right in the developer's face is a really good thing.

And it shows from the rock-solid stability of the services I've put into production with Rust. "Set it and forget it" is real. I have another service written in Python that is fairly solid. It only took 6 years of testing, fixing uncaught exceptions, and a lot of trial-and-error to get it to its current state. For comparison the Rust project was written in about 4 weeks. (Not shown here is the actual scale of utilization, but that's a different calculus.)

I have yet another confession! Rust proved to me that high-level abstractions can be used in resource constrained environments.

Prior to Rust, I used C for several years in the embedded space. Not C++, mind you. Just plain ol' C99 and C89 before that. These kinds of projects are small by definition, but they can be demanding from a different perspective. They may require a level of cleverness that you don't normally see in service-oriented systems or your typical end-user applications that run on super computers with billions of bytes of memory.

With Rust I can have these really nice abstractions like algebraic types and sane error handling. I'm also given the warm and fuzzy feeling that my code doesn't unintentionally rely on undefined behavior, that it is memory safe and data-race safe. (Exception handlers are tricky!) If I have a heap I also get access to growable collections like vectors, hash maps, and sets. Even if I don't have a heap, I can still used fixed-size variants of these collections on the stack. I can write async code using Futures without multiple stacks or multiple threads.

It may be important to point out that there are subsets of other languages that target embedded systems; micropython, tinygo, low.js, espruino, etc. I say "subset" in the sense that you shouldn't expect JavaScript to run like V8. And as a learning tool, these are great. But I am not targeting little IoT gizmos. I am building games that run on bare metal. Bare metal from the mid-90's, but no less bare metal. Having the kinds of compiler guarantees provided by Rust is incredibly important to meeting my performance and personal sanity goals.

In my day job, these qualities translate surprisingly well to large super computers and distributed systems. I have all the same basic tools that are known to work in resource constrained environments. On the one hand, I could use other frameworks or language features that use copious amounts of memory and CPU time. On the other hand, I could reap the benefits of zero-cost abstractions even though it's arguably unnecessary on these gargantuan machines. It makes sense when you understand that performance is money, memory is money, and latency is money.

In other words, if it runs on 25-year-old hardware, it can certainly run on modern hardware. And it will probably run more than 100x more efficient.

8 Likes

Rust make me more think about reference‘s life cycle,it's very different than java,c# or kotlin, when i write java,C#,kotlin,if this reference i do not need anymore,make it null,like this:"xx=null",but memory not recycling, it depending on the gc.
at beginning write rust,i am spend long time to adapt to borrow check,but i have to admit that rust let me a deeper understanding of the memory model ,and i think that is good to who from gc language too.
rust's error handling is different from the language i can.It's clever and elegant, with different ways to catch or throw exceptions, and it's a good way to locate and resolve errors.

3 Likes

Positive discouragement
Previously I think I was more likely to try to use everything a language gives. Now with rust I will consider alternatives to avoid discouraged features. (Not exclusively rule then out.)

unsafe
index
Any
RefCell
Box (glad box keyword isn't part of language.)

Still mostly a fan of copy clone though.

p.s. A wiki editor needs to add to boxing how Rust isn't using the term exactly the same. Long live UFCS. :crazy_face:

3 Likes

I have to disagree with you there, I'm still waiting on box_patterns :frowning: It would make this code about 10x nicer: https://github.com/jyn514/rcc/blob/d17b79e37be581e8e424e3691dd7042c2d2b99c4/src/analyze/expr.rs#L58

1 Like