Hi @LukeTPeterson.
When it comes to prototyping, I have different strategies, depending on what it is that I want to quickly check. E.g. when I want to know if a numeric algorithm works correctly, I use GNU octave to find that out. In order to check if the UX of a user interface is any good, I would use something like Glade. In a corporate context, I wousd use Excel or MsAccess to validate a new business process. For other things, I would sometimes hack together a shell script.
As you can see, for me, prototpying always involves using a different technology to quickly validate an assumption and iterate over one aspect of the application without building a full-fledged application.
Now when I say "quickly", you have to take that with a grain of salt: finding a good numeric algorithm can take weeks, months or even years and involves doing mathematics. Optimizing a business process can take long as well and involves discussing with the business. The point is that it allows you to focus on one aspect, ignoring the others.
So I think it depends on what you want to quickly check. Reading your post and comments, I guess that you want to quickly check the design of your software. Now, unfortunately, you cannot use another technology for that: software design in other languages doesn't translate well to Rust. So how to approach this?
One could argue that Rust is the ideal language for validating software design because, as you may have experienced, the design is validated at a very early stage, at compile time, using various restrictions. Unfortunately, it doesn't really allow to validate different aspects of the design in parallel: most problems you have to fix one by one.
So then how to iterate and find a good design? I think it's an art, just like numerical mathematics, UX or optimizing business processes. Coming up with a good design is very hard.
I can tell you my personal approach. Now, reading again what you've written, I see that my personal approach is anything but quick and dirty programming. In fact, I've learned the hard way that for me, it works best if I try not to go against the language, unless I really know what I am doing and why I need an exception. For me, things that "go against the direction of the language" are precisely Rc
, Arc
, Cell
, RefCell
, Mutex
, marking fields as pub
, unsafe
, macros and procedural macros. Higher-ranked trait bounds (for<'a>
) are a red flag. I try to avoid these. If I think I need Rc
or Arc
, this means that maybe I need to think about ownership. If I think I need Cell
, RefCell
or Mutex
, maybe I need to rethink ownership and mutability. unsafe
is for rare cases only. Macros and procedural macros: maybe I'm over-engineering.
Now I have to put a big disclaimer here: this is what works for me, for the particular type of software I work on and it doesn't necessarily apply to your situation. For instance if you're building a user interface with GTK, you will probably need some Rc
or Arc
.
For me the caveat is that, yes, sometimes I need one of these things. The art is knowing when it's justified and when not. For instance, for one of my projects, I reached for an unsafe
block because I knew for sure there was no other way around it. It works flawless (at least until now). In another occasion, I believed I needed specialization and I used macros and procedural macros to hack that into the language. I've regretted that ever since.
I hope this helps. I now realize that it doesn't really answer your question for quick and dirty programming in Rust (maybe to the contrary). Maybe the conclusion is that Rust isn't well suited for quick and dirty programming. Anyway, in an effort to help you, here are some things that I tend to do a little "dirtier" in Rust that I wouldn't in other languages:
- Module and crate hierarchy. Can be fixed later and is usually not clear from the start, so no need to think too much about that upfront.
- Accessing private fields from other structs in the same module. I do give this a second thought and I sometimes write an abstraction, but often these structs "belong together", so it's not a problem.
- Borrowing in a method instead of in the struct. I learned that this makes the design much easier many times and sometimes it even makes sense. So instead of holding on to a reference of something in the struct, I just pass it when calling the method.
I hope this helps.