Hi,
I recently started to learn Rust, coming from (nowadays) mostly Python and C/C++. As someone who enjoyed assembly coding in the good old times, I am excited to see a language gaining traction that combines being close to the machine with powerful abstractions and safety. It feels like a breeze of fresh air!
My excitement notwithstanding, I keep wondering about some design decisions of basic Rust syntax. I know that this aspect of the language went through a long phase of iterations and experimenting, so Iâm confident that good decisions were made. However, I would like to understand. I cannot answer the following by searching and reading existing discussions and documents. Perhaps someone here would be so kind to enlighten me or to point to relevant sources?
In programming in general it seems more common for function parameters to be âobservedâ than to be âmutatedâ or âconsumedâ. Indeed, in pure functional programming values never get modified. Even in an imperative language it seems a good idea to consider âobservingâ the default mode of argument passing.
However, in Rust, the syntax for borrowing is more unwieldy than the one for moving.
Rust allows to pass an argument to a function in four ways:
- move
- copy
- immutable borrow
- mutable borrow
Copy and move have the same syntax and I understand that under the hood they are the same operation and the only difference is whether the memory that has been copied is allowed to be used further as a value or not.
However, semantics seems more important than implememtation, and from this point of view an immutable borrow and a copy are the same: in both cases a value is passed on and the original owner may continue to use it unchanged. How this is implemented under the hood is anyway the choice of an optimizing compiler.
In this light, would it have been possible to base the syntax of Rust on the concepts âobserve, consume, and mutateâ as proposed in this Reddit post? It proposes an alternative syntax
observe(b);
consume(^b);
mutate(&b);
to replace Rustâs
observe(&b);
consume(b);
mutate(&mut b);
In response, one may argue that this proposal will give let a = b
the meaning of borrowing b
instead of moving it into a
. However, let
is special anyway, so perhaps a satisfactory solution could be found.
Naively, the above alternative syntax seems quite attractive. It would be not only more concise and clearer, but also more expressive. With todayâs Rust, one library for 2d vectors may choose to make its vector type âcopyâ and the basic arithmetic operations (dot product, vector addition, etc.) to take their arguments by copy. But another library for big vectors will reasonably choose to make them non-copy and the basic arithmetic operations to take their arguments by immutable borrow.
I havenât tried, but it seems difficult to write a generic function that will do some vector arithmetics and will work when parametrized with either a âcopyâ 2d vector or a ânon-copyâ big vector.
I guess that I must be missing something important, or otherwise Rust would already use something similar to the above alternative syntax...