Rust and Nim actually have the same goal here, enforcing a "mutability xor
sharing" invariant; i.e. a value can either be shared or mutable, but not both.
They just do it somewhat differently.
Rust enforces it via the borrow checker, Nim by turning assignments to mutable
locations into copies. To either work around the constraint or to violate it
generally involves jumping through extra hoops.
First, note that Nim does an automatic copy only when putting a shared value in
a mutable location, which is normally not possible in Rust, outside a move
assignment.
But you can do move assignments in Nim, too, same as it's done in C++:
template move*[T](x, y: var T) =
swap x, y
reset y
Now, nobody does it this way in Nim, because it's unnecessarily expensive and
because `shallowCopy` or a `swap` without the reset is usually more than
adequate and cheaper, even though it (temporarily) violates the invariant.
This is because the most common use case for move assignments is to store local
values in the heap, and the invariant violation ends once the local value goes
out of scope.
In order to do the same in Rust, you have to use `Cell` (which does a copy,
same as Nim`s mechanism) or `RefCell` (which adds an extra flag to the value in
order to check whether it has been borrowed or not, replacing static with
dynamic borrow checks).
Conversely, Nim can also avoid copies. Passing arguments and returning results
already avoids copying. When reading values from the heap, using `let` in lieu
of `var` also avoids copying; same goes for using `let` instead of `var`
locally if you don't mean to mutate the result.
Note that you can also avoid copying on strings and seqs in Nim if you mark
them as sharable by using the `shallow` primitive. Example:
proc main =
var a = "foo"
shallow a
var b = a # Look, ma, no copy!
You do this when you want to say that it's always safe to share this value,
e.g. because it's immutable.
Now, at this point I should say that I don't really like either Nim's or Rust's
approach, but I'm less unfond of Nim's. Here are the problems with either:
1\. Rust's approach has serious modularity problems. This is not specific to
Rust, actually, and not specific to this particular issue, either: it happens
regularly when you want to turn type systems basically into specification
languages. Traditional type systems are used, inter alia, to abstract over
implementation details. This is an important and useful compromise between
making static assurances about program behavior while retaining the benefits of
modularity. But the closer a type system comes to a fully-featured
specification language (Rust's borrow checker is essentially a proof assistant
for object lifetimes, after all), the harder it becomes to maintain that
compromise, because these type systems cannot avoid exposing implementation
details.
A simple example is globally caching the results of a function in Rust. In
order to do that, you either have to introduce copying overhead (to copy data
from/to the cache) or expose the implementation detail that data is being
shared (e.g. by wrapping the result in `Rc<T>`).
In many ways, that is still much, much better than the approach manual memory
management uses (where implicit assumptions about ownership are often invisibly
baked into a module's interface). You at least have proper documentation and
memory safety. But it's still a software engineering cost (even if it's a
lessened one) that you have to bear.
2\. Conversely, Nim's approach has two different problems. First of all, it's
invisible and involves a fair amount of magic. This trips up newcomers, but can
also lead experts to inadvertently introduce unnecessary performance overhead.
Second, for all the effort, Nim's approach only approximates the "mutability
xor sharing" invariant; you can still inadvertently violate it, e.g.
proc main =
var a = "foo"
let b = a
a[0] = 'x'
echo b # prints "xoo"