But, what happens if I have a Bunch of things and now I'm done with it?
In a language like C++ or Rust, if those things happen to be Files then when they're destroyed because I was done with the whole Bunch, the files get closed. [ This also means, although it would usually only happen intentionally in Rust, that you can leak the open files, that's what Rust's Box::leak does for example, but if it didn't exist you could do it yourself ]
I haven't written any Austral, but, if I have a Bunch of Files, am I now responsible for taking all the Files back out of the Bunch to closeFile them or else my program won't compile somehow? That seems like it'd suck for performance.
Or maybe I just can't put Files in a Bunch because they're Linear? That seems like an annoying limitation.
Or maybe I need to tell the Bunch to call closeFile on the Files when I get rid of the Bunch?
You'd take out each File from the Bunch and close it, and then deallocate the Bunch. This can be abstracted into a function.
>That seems like it'd suck for performance.
In either C++ or Rust, the exact same thing is happening. Except that you don't see it in the source code, because the compiler injects destructor calls for you at the end of a block.
But there's nothing magical about destructors. The exact same code is being executed as in Austral, only Austral has 1) no surprise control flow and 2) no hidden function calls, so you have to see the code that is going to be executed.
It might look something like this:
-- bunch has type Bunch[File]
while not isEmpty(&bunch) do
let f: File := pop(&!bunch);
closeFile(f);
end while;
disposeBunch(bunch);
Did you try this? Because while you're correct in terms of the semantics, the performance (which is why I chose that word) is quite another matter.
Specifically it's not at all uncommon for some collection (such as our arbitrary Bunch) to offer a far cheaper way to throw away the whole Bunch than to remove every Thing in the bunch one at a time.
With a destruction mechanic this is fine, the collection destroys all the Files at once, but if the user must hand roll a loop like yours - taking each File out of the Bunch and then closeFile(file) then they can't take advantage of the improved performance.
It's not about removing each element individually vs. deallocating the whole collection. It's that you have to close each file handle individually.
In C++ you might iterate over the bunch, close each file, and then deallocate the whole thing. In Austral, because of how linear types work, you have to take a linear value out of the collection, destroy it, then, when the collection is empty, you can safely deallocate it.
There doesn't have to be a performance difference between the two because popping an element from a bunch does not imply resizing the bunch or doing any allocations.
In either language, the effectful actions are the same: close n file handles, deallocate the memory for a bunch.
> It's that you have to close each file handle individually.
My emphasis. This is where the problem creeps in. Let's see that again:
> In C++ you might iterate over the bunch, close each file, and then deallocate the whole thing.
In Rust or C++ actually the Bunch will destroy and thus close all the files. Not me, I just destroyed the whole Bunch, not my problem how that works.
> There doesn't have to be a performance difference between the two
If your description had been more precise it might be apparent where a performance difference comes from. The Bunch knows the intimate details of its internal workings, so it may be able to do significantly less "housekeeping" while it is cleaning up. Without that knowledge however, when I am cleaning up the Bunch has no idea what my ultimate intent is and so it needs to do proper housekeeping.
I think you could try to mitigate that, at a cost of further language complexity, by introducing another Linear type, the ToBeDestroyedBunch which knows it is destined to be destroyed and is just here while the caller destroys the Things which were in the Bunch.
So, when I want rid of the whole Bunch I call a function with a Bunch, and I get back a ToBeDestroyedBunch. This needn't do all the housekeeping on each removal because we're obliged to dispose of it properly after we're finished and we can no longer do any other operations on a Bunch because we don't have a Bunch only a ToBeDestroyedBunch.
The Bunch API could easily include a function that takes a bunch and a function `t -> ()` that deallocates individual members, so that the logic is encapsulated in a function. You have to pass the destructor function explicitly because the language doesn't know which function is the "canonical" destructor (and a type may have multiple destructors, i.e. multiple ways to end its lifecycle).
Yes, it seems to me that the only difference is that destructors are called implicitly in c++/rust and explicitly in austral.
I wonder if IDE support could help to get the best of both worlds: the IDE could automatically add the destructors call in code where needed, with an "automatically-generated" annotation that would allow to update and remove the calls as needed. I actually kind of like the idea.
Similar things could be done for inference/type deduction.
Possibly exceptions as well.
The ide could also allow hiding the automatically generated blocks when you want to focus on the code isntead of the incidental bits.
Probably these days new languages need to come with lsp-support out of the box, so that seems the best place to add the magic.
Sure, the idea of potentially having different ends to the lifecycle makes sense to me, same way constructors aren't a thing in Rust.
And yes, I agree that this solves the problem, which was why I suggested it as one of three ways this could work if you look back up the thread. Not sure about how Austral can assure itself the Linear contractual obligations were discharged here, but I might be overthinking it.
In a language like C++ or Rust, if those things happen to be Files then when they're destroyed because I was done with the whole Bunch, the files get closed. [ This also means, although it would usually only happen intentionally in Rust, that you can leak the open files, that's what Rust's Box::leak does for example, but if it didn't exist you could do it yourself ]
I haven't written any Austral, but, if I have a Bunch of Files, am I now responsible for taking all the Files back out of the Bunch to closeFile them or else my program won't compile somehow? That seems like it'd suck for performance.
Or maybe I just can't put Files in a Bunch because they're Linear? That seems like an annoying limitation.
Or maybe I need to tell the Bunch to call closeFile on the Files when I get rid of the Bunch?