Rustic Rust for Rapid Prototyping

Reference: Make Your Rust Code More Rustic By Breaking Some Rules | HackerNoon

The above presents a strategy for using Rust in rapid prototyping scenarii by mainly making use of clone, refcell and arc in order to (temporarily) satisfy the borrow checker - a quick and dirty (and hopefully temporary - not "permanently temporary") solution.

Usage of Rust for quick prototyping is somewhat controversial, notably during the slope of the learning curve.

Proposal: allow a "rustic" or "dirty rust" in which the above strategy is implemented automatically, for example with a compiler flag somewhat between "error" and "warning" level. Only "debug" targets would be allowed, not "release". Core of the proposal is that the syntax would stay the same, the compiler would simply insert itself clone and refcell and arc.

Pros: would make easier to use Rust for rapid prototyping, with a solution that can be progressively brought in line with idiomatic rust. Cons: the risk that much code would remain in the "Rustic" state forever and never be brought into Rust proper; also, this is cited as an "anti-patterns" in Rust (Clone to satisfy the borrow checker - Rust Design Patterns)

Some other reference links:

  1. https://www.reddit.com/r/rust/comments/ptnrw8/how_useful_is_rust_for_quick_prototyping/
  2. Developing guidelines for prototyping with Rust - help - The Rust Programming Language Forum
  3. Prototyping methodologies - community - The Rust Programming Language Forum
  4. Prototyping in Rust, versus other languages -- What's missing? - community - The Rust Programming Language Forum
  5. Clone or Reference Count – Which One Is Faster | Ivanovo

(edited typos)

I don't think it's possible to introduce RefCell automatically in place of &/&mut references, because introducing RefCell (or Mutex or RwLock) makes it impossible to borrow the contents of the cell longer than the Ref[Mut] guard exists, so the automatic-cell-inserter would have to make choices of the scope of the Ref[Mut], and those choices would end up being too short or too long (because the point of this transformation would to write programs the compiler doesn't automatically understand the borrowing patterns in).

If you introduce Rc<RefCell<T>> for every single individual value, so that no long-lived borrows need to exist, then you will get a result that works, but the code written under that condition …

  • … often will not be able to be translated piecemeal into “proper” Rust.
  • … will be very expensive — full of tiny little memory allocations, but without any of the optimizations that the implementations of languages built for GC do. But that's likely fine for prototyping!
  • … will not be able to use Rust standard library elements like collection-borrowing Iterators, and their replacements will have the problems Rust avoids by design, like iterator invalidation and accidental shared mutability.

Now, I don't mean to say that it would not be useful to program in the “cell for everything” world. However, it would not be a superset of regular Rust — it would be a different language with interop (with the same syntax, and perhaps embedded in Rust via a bunch of attribute macros), so that you can incrementally rewrite your program from one to the other. And then, the question is how useful it is to build that language vs. users explicitly choosing to use RefCell in prototyping.

2 Likes

I think this is currently far too vague for me to understand exactly what you're proposing.

For example, are you inserting Rcs? Arcs? Cells? RefCells? RwLocks? How are you deciding where they go? Does this make existing code no longer compiler when you turn it on, since it changes the Send-ness of stuff?

1 Like

This sounds like another language (call it RustScript) that incidentally compiles to Rust with this lowering, but which could also have other codegen backends such as some fast compile to wasm.

2 Likes

The borrow checker may work in two stages: v1) checks performed at compile time v2) checks deferred at run time

Both ways are correct, although v1) catches errors at compilation, while v2) can only panic at runtime.

The difference is that of syntax: v2) uses a different syntax than v1) (e.g., RefCell is required).

The major part of the proposal could be reformulated as: accept behaviour v2) even with syntax of v1) via a compiler flag. This would be equivalent to having the borrow checker mechanisms deferred at runtime.

The above does not cover all cases in OP, but some of the most useful ones.

1 Like

The compile-time checks depend on lifetime annotations, to make the problem tractable and also to rule in and out what the programmer might intend — the same program can have different lifetime annotations, presenting different constraints to dependent or added code.

The run-time checks depend on inserting RefCell at the right places, which affects exactly which patterns of run-time borrowing are permitted.

It will be necessary to provide some specification of where the RefCells go. Perhaps there could be some algorithm which can translate "sloppy" lifetime annotations (that the borrow checker would not accept) into RefCells. But in any case, in order to actually implement this idea, there will need to be some algorithm for inserting RefCells (whether it is guided by lifetimes or a simpler syntactic rule), and that algorithm will affect what programs are accepted by the "Rustic" mode — it is not possible to just put them everywhere and get a program that is compatible with normal Rust.

If you can sketch out an algorithm which should be used to determine where insert the RefCells, then that will make this proposal much more concrete and potentially implementable.

1 Like