Tag: pointers

Progress Report #8

That recent change I made to speed up the processing also revealed a bug that occurred whenever a function scope was resized. This led to an overhaul of the variable storage system that may or may not have been necessary but will speed things up anyways because there are fewer function calls to make.

At the same time, I’ve decided to change some language semantics. Previously, when assigning data (such as a number) to a variable, the original function was retained, but the return was now a “constant return”. This meant that calling the function on the variable would return this “constant return” rather than the function, yet the members of the original function were still accessible. This “constant return” feature was originally designed to be a shortcut, but, ironically, it goes against the intended language design because this syntactic sugar was actually intended to be a shortcut for creating a new function. Now it will be.

Continue reading “Progress Report #8”

Advertisements

Solving the Shortcut Dilemma

Pointers are always a pain. I have found the only safe ways to work with them that both avoids memory leaks and segfaults. Unfortunately, there are some things lost that even ruin some expectations in Copper.

Continue reading “Solving the Shortcut Dilemma”

pointer~item

Pointers make using a language easy. Lots of languages exclude stack-based instantiation because the same tasks can be handled by easy-to-create dynamic variables. That said, I couldn’t program normally without them. It’s too annoying. This is one of the fundamental reasons I don’t program in Rust: pointers in Rust are tedious to work with. You have them in a bizarre sort of way, but unless you’re creating router software or other hardware drivers, you may not care about tossing pointers around abit, and your job is usually easier when you can. That said, Copper contains pointers and makes it really easy to make them. I’ve already shown pointers in the syntax tree, so if you read that part, this announcement isn’t surprising. Without further ado, let’s see an example of how to create a pointer. Get ready for one of the shortest lines of legal pointer code you will ever see:

a~b

This line creates two variables, “a” and “b”, and makes “a” a pointer to the function held by “b”. Simple, right? Like Python, if you assign a new function to “b”, “a” no longer points to the same thing as “b”. The difference is that, in this case, the function originally in “b” is destroyed. “a” is left with a pointer to an empty function container, which reports to “a” that the function is gone. Next time “a” tries to access the function, it receives the info that the function is gone, and it promptly drops the container, which is then destroyed when its reference count reaches zero. No side effects. The chain of references is broken because pointers point to a function container rather than the actual function itself.
In fact, technically all variables point to function containers. This makes it easy to convert them into pointers. The only technical difference between a pointer and a non-pointer (as far as the VM is concerned) is that one variable happens to be the “owner” of the function. When that owner drops the container (whether by being destroyed when its stack frame closes or by receiving a new value to store), the container destroys its function and waits for the other pointers to try to access it to receive the news that the function is gone.
There are a couple of nice things about this. First, when a pointer receives the bad news, its variable converts itself to a non-pointer and gains its own unique function. No null pointers, just new data. No crashes. Another nice thing is that, by not holding the function directly, function containers waste very little space. Most of the data – the scope, the parameters, the body etc. – is destroyed by the function that owns them. A huge chunk of memory isn’t being reserved until every last pointer gets the bad news that it’s off limits.
(In case you’re wondering, a~a is also valid, but it just creates a new function.)
One of the other beautiful things about all variables being pointers (to the VM) is that we can easily enforce the syntax rule that all variables are accessed the same way. In other words, the scope-opening token (.) works. No need for a silly arrow (->). The same is true when running a function in a variable that is a pointer. What happens in case of failure? – You get the same thing as if it was a body-less function: an empty function is returned. The language stays consistent.

This

Now that you know about pointers, you might be thinking about the self-reference pointer. C++ has the “this” pointer. Python has a custom-named first-passed parameter (usually named “self”) passed to functions. Other languages have their choice, however convenient or insane they may be named. In Copper, it’s named “this”. It can be used like any other variable.
The role of the self-reference pointer is to allow editing the function-object within the execution body assigned to it. (It’s more like the JavaScript “this” than the C++ one.) The following two things do the same thing:

a.b = 0
a = { this.b = 0 } a()

Notice that, in the second example, the function had to be run. The code within a function is not run automatically. In another article, I’ll show how this is useful for different paradigms.
By “editing the function-object”, what happens is that we edit the persistent scope of the object. What’s that mean? The persistent scope is a group of variables that are members of a function-object. Hence (assuming we have a print function) the following two printed lines are equivalent:

a.out = "hello"
b ~ a
print(a.out)
print(b.out)

The members are unique to the function, not the variable, so if we assign a new function to the variable, all of the old members are deleted along with the old function body, etc.
While members cannot be saved directly, they can be copied with the assignment symbol. Hence, assuming a print function, the following two printed lines are equivalent:

a.out = "hello"
b = a
print(a.out)
print(b.out)

The difference between this and the last example, visually speaking, is the “=” sign. Because this is an assignment operation, the “out” from “b” is a separate variable from the “out” from “a”.

Concluding Remarks

Pointers in Copper are the safest in any language I’m aware of firstly because they are only one type and secondly because they never cause a segfault. I’m aware some people would rather have direct failure, so warning messages are produced. Furthermore, the function are_empty() can be used to check if a function contains no members in its scope, no body, and no return data – which is the kind of function you get if function access fails for a pointer. Again, this may not be desired, and admittedly, Copper is designed for embedding, not large projects, but I intend to do some IDE integration at some point and show how it’s possible to do debugging for this and other bizarre features of Copper.

There is more to pointers than just this, but I’ll save that for another installment.

When To Use Smart Pointers

You could very easily go crazy with smart pointers. It seems like the safe idea, until you realize that you could be tying up memory everywhere. Perhaps you handle them carefully to avoid that issue. A more pressing problem is the mess of code they require to use them, such as is the case if you template your smart pointer class. Even if you are ok with that, your compiler might not be. GCC, for example, has a bug that won’t let you nest templates within each other. Usually you have to create some sort of wrapper class around a template. That makes accessing the data even more tedious.

Continue reading “When To Use Smart Pointers”