You can do this quite easily in Rust. But you have to overload operators to make your type make sense. That's also possible, you just need to define what type you get after dividing your type by a regular number and vice versa a regular number by your type. Or what should happen if when adding two of your types the sum is higher than the maximum value. This is quite verbose. Which can be done with generics or macros.
You can do it at runtime quite easily in rust. But the rust compiler doesn’t understand what you’re doing - so it can’t make use of that information for peephole optimisations or to elide array bounds checks when using your custom type. And you don’t get runtime errors instead of compile time errors if you try to assign the wrong value into your type.
Maybe once or twice a month I'll get some language issue (like no negative trait bounds, restrictions on generic const, or something related to lifetime). I might spend an hour or more on each such case.
> It's essentially impossible to write a Rust program without relying on many of its escape hatches like RefCell and unsafe, that make the borrow checker go away.
This is very contrary to my experience. I don't use cloning or various primitives (except when I really need the shared state).
I can assume that you have this opinion because you see it as a low-level problem while its solution lies at the architectural level.
Use pure functions, group data in structures that own it, get all the necessary data in the controller, process it, and return or store it.
...and using it in function calls would also be inconvenient.
Now I have a clear understanding of what is happening and how.
Nevertheless, using something like this for educational purposes maybe could help. Author of the article In the example with Id literally complains that moving makes moving.
> Second is an absolutely strict programming language, that incorporates not only memory membership Rust style, but every single object must have a well defined type that determines not only the set of values that the object can have, but the operations on that object, which produce other well defined types. Basically, the idea is that if your program compiles, its by definition correct.
That is Rust? This is how his system of types and traits works.
Look at vectors or hash maps. This is a perfect example of the Rust philosophy. Namely, writing complex low-level abstractions that offer a convenient and reliable high-level API.
It uses unsafe, but few people understand what it really is. Rust has clear invariants (for example, that a pointer to T always points to an existing and valid T) that are enforced by the compiler. Using unsafe code is shifting the enforcement of these invariants from the compiler to the programmer. The advantage compared to C is that unsafe code is limited to undefined blocks that are easy to test.
The data structure you describe should be done exactly this way. And it is not a task for the average programmer.
In fact, at each layer, if you want to propagate an error, you have to convert it to one specific to that layer.