At 5:05 +0200 24/7/05, Frank Heckenbach wrote:
Peter N Lewis wrote:
I'd love to start using New and Schema and such, but the best solution seems to be the Trap technique and that's some ugly code to have any place you want to use New and not crash out.
With a nil-return approach you'd really have to check each allocation, of course ...
True, although I'm pretty used to that, and if you are going to have anything other than "program dies on allocation failure" memory handling, then you have to have *some* code. The question is what is reasonable and simple and clear.
I think checking for nil before initialization would not be that hard to do in the compiler. The main drawback I see would be that it would add some code to all programs because the compiler can't know whether the allocator that's active at runtime may return nil. (And no, I don't want to add a compiler option. This would create quite an ugly mess of compile-time settings and runtime behaviour interdependence and just invite hard to find bugs.) OTOH, the check would, of course, only be necessary if the type needs initialization at all (which the compiler knows), so most allocations are not affected -- only files, schemata and objects ATM. So perhaps it's not that bad. Such things are not allocated and initializalized in tight inner loops usually, anyway.
It sounds reasonable to me. I'd even be happy with this applying only to the New as function case if that would help, ie:
New(s,57) gives a runtime error on failure, and so the compiler knows that it need not bother checking.
s := New(String,57) potentially returns nil, so the compiler checks.
I'm not sure if that is reasonable. It makes a certain kind of sense in some respects - New as a procedure has no return value and thus nothing to report, New as a function returns a pointer (even for reference objects anyway) and therefore can return a success/failure result.
And then there's a problem with allocation in the RTS. I don't think there are so many places, so I could probably take care of all of them and check for nil-returns. The question is, of course, what to do then. Unless there's a reasonable default behaviour that can be done without allocating memory (rarely, I'd suppose), I think the best and safest thing to do is raise a runtime error. And there we are again ...
If there is no way to return an error as an alternative to runtime failure, then runtime failure is the only option.
I checked and there are about 20 in units and about 15 in rts. Most look like they have not much choice but to runtime error, while some, like LocaleConv for example, look like they could easily pass the failure on up the line.
I guess by having the two forms of New (function and pointer) behave as described above, it might work quite well in that you can easily make the decision in the rts or in user code - if you don't want to (or cannot) deal with the error, then call New as a procedure; if you are willing to deal with the possible memory allocation failure, then call New a a function and honour the possibly nil result.
I did think of another hack, which was to pre-allocate a chunk of memory larger than the largest New command, and then have GetMem return that for failure, then rather than testing for nil, I could test of that safe return value. It's ugly, but probably the simplest solution that provides clean New code.
It's a kludge. I think it would work, but I wouldn't like to keep it permanently ... ;-)
True, but actually, way back in the days of the first Mac, when we had 128K memory, the heap manager had a concept of a "grow zone" which basically did just this. At the start, you allocated a spare chunk of memory, enough so that no critical systems would fail (eg putting up a dialog to tell the user that all hope was lost), and then if the heap manager was going to fail a memory request, if released this block of memory and retried the request, and then later in your main loop, you detected that the memory had been released and warned the user/saved files/gracefully quit/whatever. You still had to be careful for any big allocations (eg reading in a file), but you didn't have to be paranoid about every possible rts allocation.
So it is not an idea completely without precedent. Peter.