I was going to talk about this on IRC but I figured out a post would be more appropriate. This is also my first post on this forum so hello, nim-community. :D I usually don't get involved in such stuff, but since Nim is running towards 1.0 and I have huge expectations for it, I'm making an effort. :)
I'm a bit disappointed about ptr/ref constness and I wanted to talk about it. As far as I understand it, constness in nim is expressed using:
As long as the variable is a "value-type" (an object in nim-talk) the const guarantees applies. The problem is that when using a ref/ptr, the constness applies to the ptr and not the pointed object.
In C++ speak I have the feeling that:
Object const* ptr; //pointer is mutable, pointed object is const
//cannot be expressed in nim AFAIK
is far more usefull, and in general a stronger guarantee, than:
Object *const ptr; //pointer is const, pointed object is mutable
//equivalent to let ptr/ref, and "non-var" ptr/ref parameters in nim
Now an example of why it weakens the guarantees while using nim:
Let say I create an int wrapper like this (obviously useless, but it's an example), and an associated proc that prints it, without modifying it. The second line of this proc is an error and is catched by the compiler.
type IntBox = object
i:int
proc log(box:IntBox)=
echo $box.i
box.i = 2 # DOES NOT compile: box is not var, i'm not allowed to modify it.
# Proc signature says it wont modify box: strong guarantee enforced at compile time.
I go further into development and discover that my IntBox instances should be shared between multiple locations. I need to allocate them on the heap, and thus make the following change in type declaration.
Note that the semantic of my proc is unchanged: it shouldn't modify the box it's applied to.
type IntBox = ref object
i:int
proc log(box:IntBox)=
echo $box.i
box.i = 2 # now it DOES compile, and proc is allowed to modify box!
# Strong guarantee is gone :(
I'm not sure why things are that way and there's probably a good reason for it. I bet on consistency, as ptr/ref are values themselves, and it seems logical that constness applies to the same level than for other values.
But still, nim's design is about practicality more than consistency (am I wrong?), and here I think we loose a huge practical guarantee.
I'm not sure about the solution, but could we imagine that a ptr/ref constness expands to its pointed value? I'm still a newcomer in nim and might be mistaken, but I wanted to talk about it. :)
I agree that such a stronger guarantee would be much more useful. Apaarently, though, one cannot do that without some kind of lifetime tracking. The reason is the following: how do you prevent this?
type IntBox = ref object
i:int
proc log(box:IntBox)=
echo $box.i
var foo = box
foo.i = 2 # this is ok because `foo` is mutable
# Strong guarantee is gone :(
This may seem like a simple case, but then consider
type IntBox = ref object
i:int
proc evilModifier(box: var IntBox) =
box.i = 2
proc log(box:IntBox)=
echo $box.i
var foo = box
evilModifier(foo) # no local change in sight, and yet...
# Strong guarantee is gone :(
So, without a way to track lifetimes at compile time, as it happens in Rust, one cannot prevent the modification of pointed objects. Of course, actually tracking this stuff introduces a lot of complexity in the language.
My writetracking pass actually solves this but it would be yet another effect and people have trouble with the gcsafe effect already and its implications. At this point we don't need more features that don't pull in their weight.
That said, I am thinking about mapping func f() to proc f() {.noSideEffect, writes: []}. Note that the keyword "func" currently has no meaning. So ... Nim is quite future proof in this respect.
To reply to Sendell's question:
The constness in Nim is not the property of the type, it is property of the variable/argument. It is good reason to keep things this way. It allows to stay away from C++ problems.
For example, in C++ you can find that std::vector<const double *> and std:vector<double *> are completely incompatible types which is much bigger problem.
I would also be interested in a bit more information on writetracking, it seems like a perfect solution to the constness problem. How can it be enabled and what is the current status/plans?
At this point we don't need more features that don't pull in their weight.
That's surprising. I think solving the ref constness problem for good would have a huge benefit, it always felt like a big missing piece in Nim. I was hoping this would make it into version 1, especially because it sounded like writetracking is the foundation for move semantics etc.
I was hoping this would make it into version 1, especially because it sounded like writetracking is the foundation for move semantics etc.
There is some overlap but sink parameters do not need write tracking. Write tracking computes "may write to", sink means "consumes exactly once".
1 year later, I'm still worried about that missed opportunity of stronger immutability guarantees in Nim.
The whole distinction between var and let feels useless without this. When looking at a proc signature, I have no way to know if its parameters will be modified, unless they are value-types and let parameters. I like the new "func" keyword, but we could make it even closer to what a pure-function should be by implementing this, and forbidding var params for funcs.
I'm not aware of the implementation issues, but Araq said it should be possible thanks to his write-tracking pass. It also feels like something that should be introduced before 1.0, since it will break things, so...
...may I push this idea again ? :)
...may I push this idea again ? :)
You may and I'm hearing you but in the longer run ref usage should be discouraged and then the problem disappears too. And solving problems by avoiding some language features is much more elegant than yet-another-feature IMHO.
Pointers are like goto, too powerful and need to be tamed and hidden within "structured data types" much like goto was tamed with "structured programming constructs" (if, while etc).
What are "structured data types"? Essentially everything that is tree-like.
This is the common sense in design I've come to expect.
This may come off as blunt, but if someone wants nim to be rust, they should just go use rust. Use what works for you.