How mutable is immutable?

Don Clugston dac at nospam.com
Thu Oct 18 01:08:56 PDT 2012


On 17/10/12 18:02, Timon Gehr wrote:
> On 10/17/2012 01:49 PM, Don Clugston wrote:
>> On 01/01/12 13:50, Timon Gehr wrote:
>>> On 01/01/2012 10:40 AM, Denis Shelomovskij wrote:
>>>> So, I'm a function `f`, I have an `immutable(type)[]` argument and I
>>>> want to store it for my friend `g` in an TLS variable `v`:
>>>> ---
>>>> string v;
>>>> debug string sure;
>>>>
>>>> void f(string s) { v = s; debug sure = s.idup; }
>>>> void g() { assert(v == sure); }
>>>> ---
>>>> I also store a copy of `s` into `sure` for my friend to ensure
>>>> immutable
>>>> date hasn't been mutated.
>>>> Can my friend's assertion ever fail without breaking a type-system?
>>>> Sure. Just consider this:
>>>> ---
>>>> void main() {
>>>> auto s = "abba".idup;
>>>>     f(s);
>>>>     delete s;
>>>>     g();
>>>> }
>>>> ---
>>>> Is it by-design? Looks like deleting immutable (and const because of
>>>> implicit conversion) data should be prohibited.
>>>> OK. Let `delete` be fixed. Can we still fail?
>>>> ---
>>>> void h() {
>>>>     immutable(char)[4] s = "abba";
>>>>     f(s);
>>>> }
>>>> void main() {
>>>>     h();
>>>>     g();
>>>> }
>>>> ---
>>>> Damn! So, what can we do with it? Not sure, but I have a proposal.
>>>>
>>>> Fix it in language:
>>>> * disallow `delete` of const/immutable data
>>>> * disallow immutable data on the stack
>>>>
>>>> This makes data really immutable if I don't miss something. Anyway, I
>>>> want `immutable` qualified data to be immutable without breaking a
>>>> type-system (if one do it, its his own responsibility), so some changes
>>>> should be made (IMHO).
>>>
>>> You are using unsafe language features to break the type system. That is
>>> not the fault of the type system.
>>>
>>> '@safe:' at the top of the program should stop both examples from
>>> working, it is a bug that it does not.
>>
>> That's the point -- *which* checks are missing from @safe?
>
> Escaping stack data and arbitrarily freeing memory are not operations
> found in memory safe languages.

HOW do you propose to check for escaping stack data?

>> But I'm not sure that you're right, this looks broken to me, even
>> without @safe.
>>
>> What does it mean to create immutable data on the stack? The stack is
>> intrinsically mutable!
>
> So is the heap.

No it is not. Data on the stack *cannot* survive past the end of the 
function call. Data on the heap can last forever.

> What does it mean to garbage collect immutable data?

 From the point of view of the application, it doesn't happen. There are 
no observable semantics. It's merely an implementation detail.

> What does it mean to allocate an 'int' on the stack?
>
>> What does it mean to delete immutable data?
>
> Deallocate the storage for it and make it available for reuse.
> Accessing it afterwards leads to arbitrary behaviour. This is the same
> with mutable data. As the program may behave arbitrarily in this case,
> it is valid behaviour to act as if immutable data changed.

No, you've broken the type system if you've deleted immutable data.
If I have a reference to an immutable variable, I have a guarantee that 
it will never change. delete will break that guarantee.

With a mutable variable, I have no such guarantee. (It's not safe to 
allocate something different in the deleted location, but it's OK to run 
the finalizer and then wipe all the memory).

>> I think it's reasonable for both of them to require a cast, even in
>> @system code.
>>
>
> The implementation of the 'scope' storage class should be fixed. We
> could then require an unsafe cast(scope) to disable prevention of stack
> address escaping.

No we can't. f cannot know that the string it has been given is on the 
stack. So main() must prevent it from being given to f() in the first 
place. How can it do that?

void foo(bool b, string y)
{
   immutable (char)[4] x = "abba";
   string s = b ? x : y;
   f(s);
}

Make it safe.


> Rust's borrowed pointers may give some hints on how
> to extend 'scope' to fields of structs.

I think it is more fundamental than that.

> As to delete, delete is as unsafe when the involved data is immutable
> as when it is mutable. Why require an additional cast in one case?

This is not about safety.
Modifying immutable data breaks the type system. Deleting mutable data 
does not. AFAIK it is safe to implement delete as a call to the 
finalizer, followed by setting the memory to T.init. Only the GC can 
determine if it is safe to reuse the memory.

Deleting immutable data just doesn't make sense.


More information about the Digitalmars-d mailing list