Prototyping in Rust, versus other languages -- What's missing?

Maybe something like a super low-boilerplate garbage collector crate with macros maybe for limiting the amount of typing necessary to create garbage collected types or something like that would be useful.

1 Like

I'm going to challenge this assumption that prototyping in Rust is slower/harder than prototyping in another language.

People are taking it as a given, but as someone who uses it all the time for personal projects and even snuck it into upcoming projects at work, I just don't see it.

Rust Lets you Iterate on the Big Picture

A massive advantage that prototying in Rust has over most other languages is that you can use the language's strong type system to try out different architectures and solutions and very quickly know whether that'll work at a larger scale.

I'll often write out a bunch of traits and types and just put a todo!() in places I don't want to fully implement yet, and then take a step back and ask myself things like "Will this architecture scale to the full application?", and "Does the way data flows through my prototype indicate possible issues with inter-component coupling or separation of concerns?".

A week ago I threw away 2-3k lines of Rust because I realised the way things were designed would make refactoring and introducing new functionality hard down the track. You could feel the management of references starting to get a bit hairy (e.g. one component temporarily needs access to X, Y, and Z, so we create a temporary struct which holds references to them, but that struct can't live for too long because the next component will also need mutable access to Z and Q), and adding new code felt a lot like shotgun surgery.

This project will probably be 50-100k lines, so being able to know that my design was no good after a week or two is a lot better than if we'd prototyped in a less rigid language like Python and not discovered those fundamental issues until several months later because Python has a GC.

The Compiler is Slow

On another project we use async code and end up pulling in half of crates.io (tokio as an async runtime, tonic for gRPC, actix for actors, several proc macros for nice-to-have features etc.), and the first build of the day takes about 5 minutes to compile 300-odd dependencies.

However, as long as I'm on that happy path of only touching my main crate, the combination of incremental compilation, cargo watch, and rust-analyzer mean within 1 or 2 seconds of saving I'll have feedback on whether I've introduced any errors. Then a couple seconds later I'll be able to look over at my cargo watch terminal and know whether my tests pass.

This is about the same amount of time I need to take a mouthful of coffee so it works out really well.

This matches what I've seen in my own experience.

I can write functionality in Rust at about the same rate as Python, sometimes considerably faster for domains where you can express things in the type system (state machines, parsing, geometry, etc.) because then it just becomes a case of inserting the correct code to make things compile.

12 Likes

I also like prototyping in Rust because I like the structure of the type system and the safety of the aliasing rules. I know that when my function takes &mut self, no one else can modify self while I'm dealing with it. I know that when my function takes an owned value, the value is mine to do whatever I want with and I don't need to think about what any other function could be doing with the data.

On the other hand, in Python (or other dynamic languages, Python is just the one I use most), I never know when I own the data, or whether I have a reference to someone else's data. I have to make sure that I copy the data (how do I do a deep clone?!) before I modify it if it's a reference that another function is loaning me.

The existence of a strong type system, including the borrowing rules, in Rust means that I don't have to worry about many of the things I have to worry about in dynamic languages, which means that I can move faster when prototyping something. Even if I make a mistake with how I'm passing data around, the compiler will catch it for me.

3 Likes

how do I do a deep clone?!

from copy import deepcopy

1 Like

I'll add that when "prototyping" with rust, I generally make everything owned and clone a lot. It removes most of the borrow checker difficulties, and I can refactor if performance turns out to matter.

That said, for some things where plotting is a crucial aspect of checking, I'll do it in Python.

4 Likes

I've gotten around to that blog post I've promised. I usually put blog posts on reddit, so any comments to the article itself are also welcome there: https://www.reddit.com/r/rust/comments/iwbp4d/throwaway_code/

5 Likes

That' a nice write up on speedily producing code in Rust. Use clone, Rc, Arc, the anyhow crate, logging, etc.

Oddly enough, apart from the advice not to use Tokio, that is pretty much how all the Rust code I have set running in production in my first year of Rust has been created :slight_smile:

Mostly that is how it will stay. For various reasons:

  1. I want to make my sources understandable by the non-rust people in the company so that they have some chance of understanding what is going on even if they could not change it at this time. I don't want to frighten them off Rust with all kind of illegible life time tick marks, trait bounds and so on.

  2. Unless it turns out performance has a bottleneck somewhere it's not going to get any attention. It's that "premature optimization" thing. Our Rust runs many times faster than similar things we have done in node.js so we are happy.

Of course if I were ever to produce a crate to publish for others to use it would get a lot more attention.

My gripe with using Rust for prototyping is that it is usually something in the Rust world I want to try out during a lunch break. Some crate of other usually. It can be impossible to fathom how to use it from trying to read the docs. Unless there is an example of what one wants it's hopeless. Or it ends up in hours of experimenting, asking questions here, experimenting some more.

A case in point for me just now is making use of the connection pool possibilities in Rocket with a postgres database using TLS. I have given up on that two or three times now. Still none the wiser.

Contrast to the Python or node.js worlds where usually there is a working example of what one wants to start with, boom one is up and running with the confidence to proceed further.

7 Likes

I can't help but laugh at how much I can relate to this. I was recently playing around with trying to make a graphql api with juniper and both Actix' docs and the lack of information about GraphQL in general made me quit a few times.

I wish everyone took a more example first approach to docs. If the docs don't have a good example I just head straight to the git to check the examples folder.

1 Like

I agree about the value of examples. I learned Java when 1.0 first came out largely by reading the examples in the Java Library books.

I can't really put my finger on where the problem is.

On the face of it everything one needs to know is very well documented. I'm sure it's all there.

But somehow stitching together what one wants from all the descriptions of traits and enums etc is unfathomable.

It is so far removed from the old days of "here is a library and here are all the functions you can call" or even "here is a class and here are all the methods you can call"

I have heard it said that such functional composition is as easy as building with Lego blocks. I don't see it yet. Always one has to get the right pieces with the right types exactly lined up before they will fit together. Like a hyper complex jigsaw puzzle. It does not help that one has no clue where to look for the right pieces that might fit what you have in your hand.

1 Like

I've been there. Not as much with Rust specifically, but with the same issue. Because the developer technically mentioned everything necessary, the docs are actually less likely to be fixed because they "aren't broken". It's like a kind of documentation "trap" I've seen some projects fall into.

That's where the "[insert project name] book" model is actually very useful and implemented for a number of Rust projects. Lots of times the mdbook documentation is in like a "Getting Started"/"How To" format that is very helpful to get immediately acquainted with a tool.

I think Rust might suffer a lot more from this kind of lack of documentation than say JavaScript because it isn't crazy over hyped and popular like JavaScript is and because JavaScript doesn't as much have automatic API documentation it makes the example-based docs a necessity.

Thankfully the API docs in Rust are amazing and every single Rust crate gets documented because of docs.rs which is incredible, but, still, we also need good examples.

I'd like to expand on and reinforce what @zeroexcuses said upthread.

For the several years I've been using Rust, prototyping an algorithm or idea has not been about the language for me. Instead, my decision has mostly come down to the libraries -- aka the 90% of the code I am trying not to write as I explore an idea.

  • I once used Python for mini web UIs due to flask. Since Rocket added some features I needed in version 0.4, I use Rust.
  • I used Python for several command line tools due to its built-in gdbm bindings and arg parser. Between clap and sled, and I am re-writing them in Rust.
  • I wrote a number of bulk file processing scripts in bash. Then I discovered walkdir, re-wrote them in Rust, and got a 10x (!!) speed increase.
  • I still write HTML scraper tools in Python, due to BeautifulSoup. I might write my next one in Rust once I find a nice wrapper around html5ever. (My current candidate is scraper.)

Just having a rough or not-yet-1.0 crate has been enough to make me choose Rust. In fact, I often contribute the missing features I need once I get further along.

In conclusion, what's missing to make Rust a prototyping language? The crate that does even 50% of whatever I want to work on next.

If you build it, I will PR, and then choose Rust!

8 Likes

I feel your pain, @ZiCog. I worked with a brilliant guy who wrote documents nobody could make any sense of. The problem was that once you did figure out what he was saying, you couldn't explain things any more clearly than the original document.

That experience suggests an experiment. Take one of those cases you came to understand only after a lot of effort, and see if you can explain it more clearly.

6 Likes

My experience with writing docs certainly agrees that it is very difficult to get right.

1 Like

I find writing docs as I code is helpful (or interleaved with coding), because if it's taking 5x the code length to explain how to use the code, and you start to think of all the support grief you're going to get from confused users, and then you see a way it can be solved by simplifying the API or config file, or giving clearer diagnostics or whatever, then that's a clear win and it improves things for everyone. So if it's hard to explain, maybe the design wasn't the best.

Another thing from way up the thread about needing a GC for prototyping -- I wonder how wise that is, because in the end the chances are that it won't be possible to translate what you've prototyped into low-level efficient Rust. Maybe that's not the aim anyway, in that case, no problem. But still, I like the approach above from @Michael-F-Bryan about prototyping interfaces as a first step, since Rust can do all its low-level borrowing checks on traits and method signatures (so long as you're correct in your belief that you can eventually implement the method within that signature).

6 Likes

Writing documentation while you code is useful, but you will always find the need to write more of it afterwards if you have enough users, especially the pieces that are not API docs. For example, when we started writing the Tokio tutorial, we already had rather good API docs, but that just was not enough.

1 Like

I agree with @jimuazu about writing documentation as you code. In 1996 I started working on a project in Perl. I quickly realized that Perl is a write-only language. (I have a colleague who produces readable Perl, but I haven't figured out how he does it.) My solution was literate programming using nuweb. I liked that my "source" code was a LaTeX document that contained both the documentation and the Perl. It took some discipline to keep the text and code in synch, which in my experience is no different than with any form of documentation.

Yes, but the fundamental thing is that the coder is spending some time switching to the user's perspective and back during the prototyping/design/coding process, using whatever means suits them. I find that starting to write some docs puts me in that frame of reference, but probably there are other ways. The Rust RFC "How do we teach this?" thing is the same idea. I don't mean that it's necessary to write all the docs and tutorials at the same time as coding. But giving it at least some attention at design/coding time improves things, IMHO.

2 Likes

I think the issue with prototyping in Rust is not just the ownership system vs GC, but the way the language is designed around it.

Recently I spent quite a long time writing some simple string manipulation code in Rust. I don't remember exactly how long, but half an hour might be an underestimate. I could write Python code that did the same thing in only a minute or two from memory without looking anything up.

I find when coding in Rust that I have to consult the API docs almost constantly. What was the method to get an item from a hash map when you have a reference to an owned key and the value is Copy and you need a reference to an option and the moon is in Jupiter's quadrant again? (And that's just the minimum, there's also usually tons of trial and error trying to get the references in the right places when it comes to pattern matching or lambdas). I do have more experience with Python, but it's not just that - Python really is simpler, and that's because the APIs aren't designed with as many concerns in mind and as many performance corner cases to deal with.

For any particular task, there's a dozen ways you might want to do it in Rust with different APIs to match because the language is designed to accommodate low level performance concerns. For example, in Python, the standard library map function will just return a heap allocated list - no worries about slices or arenas or borrows or feeding the collect<> turbofish or whatever. And there's only one version of that function and all the libraries are written in the same style so they work well together. (Well in Python 2 anyway, Python 3 screwed that up to some extent by randomly changing everything to iterators)

Having a "standard model" of how values are represented in Python also means that the language is optimized for those use cases. Everything is heap allocated, owned, and garbage collected. Most functions work with lists and dicts and None. There's only one kind of int, and it doesn't overflow. All the strings are immutable and so on. Rust makes it theoretically possible to do the same thing, but there's a huge amount of boilerplate. Even working with a simple Box is painful in Rust thanks to how much I have to liter my code with Box::news and random punctuation barf. And that's just the simplest case.

This means that when writing code where performance is not a concern, the Pythonista can travel around the world while the Rustacean is still getting their socks on. The Rust code is faster and usually more maintainable in the long run, but in many cases, that doesn't matter. Rust is just optimizing for things that aren't relevant to prototyping.

2 Likes

I can sympathize with a lot of what you say there. But I find that an odd analogy. I would not like to fly around the world on Python Air :slight_smile:

I guess a lot of this discussion hinges on what one expects from a "prototype".

Sometimes I have build little proofs of concept in C/C++/Rust exactly because performance matters and I want to quickly check whether some new technique, library, data layout, etc is actually likely to do better. Rather than mess with the whole program it will eventually live in.

On the other hand if I want to prototype some new web thing for a demo coming up quickly, I'm likely to do it in node.js.

Sometimes a cardboard and sticky tape model will do. Sometimes you need the engine as well.