6 ms·
Did you choose "No destructors" because of their effect on static analysis? -- Why are they an anti feature?
by tsegratis 4y ago
Did you choose "No destructors" because of their effect on static analysis? -- Why are they an anti feature?
- zetalyrae 4y agoDestructors, exception handling, and the type system are all tied together, so I'm not sure where to begin explaining. These two sections in the rationale provide a complete explanation: https://austral.github.io/spec/rationale-linear-types https://austral.github.io/spec/rationale-linear-types https://austral.github.io/spec/rationale-error-handling https://austral.github.io/spec/rationale-error-handling But basically: there are no destructors because there is no exception handling, and therefore no stack unwinding. The reason there's no exception handling is that the semantics (and implementation) of exception handling are incredibly complicated, and they make the code harder to reason about. A design goal in Austral is "no hidden control flow". If it's not on the source code, it isn't happening. Linear types are also not compatible with exception handling (which is why Rust uses affine types, sort of). In place of destructors you have functions that consume linear values. These are ordinary functions and have to be called explicitly, calls to them are not inserted by the compiler. For example, imagine that File is a linear type that holds a file handle. You might have something like: let f: File := openFile("foo.txt"); -- code etc etc. closeFile(f); Here, closeFile is a "destructor" in the sense that it takes a value of a linear type and returns nothing. It's not a destructor in the C++ sense because the call is not inserted for you at the end of a block or during stack unwinding.
- deleted 4y ago[deleted]
- tsegratis 4y agoOkay, nice Overall I'm impressed with your clarity of design -- coming from a language designer of many years, for whatever that is worth ;)
- zetalyrae 4y agoThanks! I spent, I think, an embarrassingly long time on design. But I can at least say that though it is not perfect, it is internally coherent.
- tialaramex 4y agoBut, 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?
- zetalyrae 4y agoYou'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);
- tialaramex 4y agoDid 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.
- ArrayBoundCheck 4y agoI dislike zig for lack of destructors and "no hidden control flow". I'll probably dislike your language too but is there one big thing difference from zig and austral?
- zetalyrae 4y agoThe crucial difference is this[0]: >It is the Zig programmer's responsibility to ensure that a pointer is not accessed when the memory pointed to is no longer available. Note that a slice is a form of pointer, in that it references other memory. Zig is very much a manually-managed language. Austral has manual memory (and resource) management, but has type-system tools to help you ensure correctness. [0]: https://ziglang.org/documentation/master/#Lifetime-and-Ownership https://ziglang.org/documentation/master/#Lifetime-and-Owner...