Mutable enums

Timon Gehr timon.gehr at gmx.ch
Mon Nov 14 11:59:50 PST 2011


On 11/14/2011 08:37 PM, Steven Schveighoffer wrote:
> On Mon, 14 Nov 2011 13:37:18 -0500, Timon Gehr <timon.gehr at gmx.ch> wrote:
>
>> On 11/14/2011 02:13 PM, Steven Schveighoffer wrote:
>>> On Mon, 14 Nov 2011 03:27:21 -0500, Timon Gehr <timon.gehr at gmx.ch>
>>> wrote:
>>>
>>>> On 11/14/2011 01:02 AM, bearophile wrote:
>>>>> Jonathan M Davis:
>>>>>
>>>>>>> import std.algorithm;
>>>>>>> void main() {
>>>>>>> enum a = [3, 1, 2];
>>>>>>> enum s = sort(a);
>>>>>>> assert(equal(a, [3, 1, 2]));
>>>>>>> assert(equal(s, [1, 2, 3]));
>>>>>>> }
>>>>>>
>>>>>> It's not a bug. Those an manifest constants. They're copy-pasted
>>>>>> into whatever
>>>>>> code you used them in. So,
>>>>>>
>>>>>> enum a = [3, 1, 2];
>>>>>> enum s = sort(a);
>>>>>>
>>>>>> is equivalent to
>>>>>>
>>>>>> enum a = [3, 1, 2];
>>>>>> enum s = sort([3, 1, 2]);
>>>>>
>>>>> You are right, there's no DMD bug here. Yet, it's a bit surprising to
>>>>> sort in-place a "constant". I have to stop thinking of them as
>>>>> constants. I don't like this design of enums...
>>>>
>>>> It is the right design. Why should enum imply const or immutable? (or
>>>> inout, for that matter). They are completely orthogonal.
>>>
>>> There is definitely some debatable practice here for wherever enum is
>>> used on an array.
>>>
>>> Consider that:
>>>
>>> enum a = "hello";
>>>
>>> foo(a);
>>>
>>> Does not allocate heap memory, even though "hello" is a reference type.
>>>
>>> However:
>>>
>>> enum a = ['h', 'e', 'l', 'l', 'o'];
>>>
>>> foo(a);
>>>
>>> Allocates heap memory every time a is *used*. This is counter-intuitive,
>>> one uses enum to define things using the compiler, not during runtime.
>>> It's used to invoke CTFE, to avoid heap allocation. It's not a glorified
>>> #define macro.
>>>
>>> The deep issue here is not that enum is used as a manifest constant, but
>>> rather the fact that enum can map to a *function call* rather than the
>>> *result* of that function call.
>>>
>>> Would you say this should be acceptable?
>>>
>>> enum a = malloc(5);
>>>
>>> foo(a); // calls malloc(5) and passes the result to foo.
>>>
>>> If the [...] form is an acceptable enum, I contend that malloc should be
>>> acceptable as well.
>>>
>>
>> a indeed refers to the result of the evaluation of ['h', 'e', 'l',
>> 'l', 'o'].
>>
>> enum a = {return ['h', 'e', 'l', 'l', 'o'];}(); // also allocates on
>> every use
>>
>> But malloc is not CTFE-able, that is why it fails.
>
> You are comparing apples to oranges here. Whether it's CTFE able or not
> has nothing to do with it, since the code is executed at runtime, not
> compile time.
>

The code is executed at compile time. It is just that the value is later 
created by allocating at runtime.

enum foo = {writeln("foo"); return [1,2,3];}(); // fails, because 
writeln is not ctfe-able.


>>
>>
>>> My view is that enum should only be acceptable on data that is
>>> immutable, or implicitly cast to immutable,
>>
>> Too restrictive imho.
>
> It allows the compiler to evaluate the enum at compile time, and store
> any referenced data in ROM, avoiding frequent heap allocations (similar
> to string literals).
>
> IMO, type freedom is lower on the priority list than performance.
>
> You can already define a symbol that calls arbitrary code at runtime:
>
> @property int[] a() { return [3, 1, 2];}
>
> Why should we muddy enum's goals with also being able to call functions
> during runtime?
>

As I said, I would not miss the capability of enums to create mutable 
arrays a lot. Usually you don't want that behavior, and explicitly 
.dup-ing is just fine.

But I think it is a bit exaggerated to say enums can call functions at 
runtime. It is up to the compiler how to implement the array allocation.

>>
>>> and should *never* map to an
>>> expression that calls a function during runtime.
>>>
>>
>> Well, I would not miss that at all.
>> But being stored as enum should not imply restrictions on type
>> qualifiers.
>
> The restrictions are required in order to avoid calling runtime
> functions for enum usage. Without the restrictions, you must necessarily
> call runtime functions for any reference-based types (to avoid modifying
> the original).

Yes, I don't need that. But I don't really want compile time 
capabilities hampered.

enum a = [2,1,4];
enum b = sort(a); // should be fine.

auto c = a;
// sort(c); // don't care a lot if this works

>
> Note that I'm not saying literals in general should not trigger heap
> allocations, I'm saying assigning such literals to enums should require
> unrestricted copying without runtime function calls.

Yes, I get that. And I think it makes sense. But I am not (yet?) 
convinced that the solution to make all enums non-assignable, 
head-mutable and tail-immutable is satisfying.

>
> I don't think you would miss this as much as you think. Assigning a
> non-immutable array from an immutable one is as easy as adding a .dup,
> and then the code is more clear that an allocation is taking place.
>

It would be somewhat odd.

enum a = [2,1,4];
enum b = sort(a.dup); // what exactly is that 'a.dup' thing?

enum c = a.dup;   // does this implicitly convert to immutable, or what 
happens here?
enum d = sort(c); // does not work?

enum e = foo(a.dup, b.dup, c.dup, d.dup);



More information about the Digitalmars-d-learn mailing list