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"
    

Reply via email to