www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Making array elements truly const/immutable

reply Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
Hello all,

While playing around I noticed something that rather disturbed me about 
const/immutable's relationship with arrays.  If I do e.g.

     import std.stdio;

     void main()
     {
           immutable(int)[] foo = [1, 2, 3, 4, 5];

           int[] bar = cast(int[]) foo;

           bar[2] *= 2;

           foreach(b; bar)
                 writeln(b);
           writeln();

           foreach(f; foo)
                 writeln(f);
     }

... then the tweak to bar will also affect foo.

Now, it seems fairly evident why this is -- I'm taking something that is 
effectively a pointer to (immutable int) and casting it to a pointer to int,
and 
so of course the latter can mutate the data contained.  I can even see
potential 
applications for this kind of cast.

But, 2 questions: (i) is there a way to mark an array as "under no
circumstances 
allow anything to modify the contents" in a way that _can't_ be cast away like 
this? and (ii) given that I can use instead bar = foo.dup to copy foo, is this 
guaranteed to produce a _copy_ or is it smart inasmuch as the compiler will 
check if bar is actually mutated and so only create a duplicate if needed?

The motivation is that I want to have a class which contains a dynamic array as 
a private member variable, but which is readable through a class property. 
i.e. 
something like

     class Foo
     {
         private int[] _foo;
          property immutable(int)[] foo { return _foo; }
     }

... but where it's guaranteed that no external agent can touch the inside of 
_foo.  So, yes, I could return _foo.idup, but I'd like to be able to guarantee 
that extra arrays won't be allocated unless strictly necessary.
Aug 02 2012
next sibling parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Thursday, 2 August 2012 at 08:56:42 UTC, Joseph Rushton 
Wakeling wrote:
 But, 2 questions: (I) is there a way to Mark an array as "under 
 no circumstances allow anything to modify the contents" in a 
 way that _can't_ be cast away like this? and (ii) given that I 
 can use instead bar = foo.dup to copy foo, is this guaranteed 
 to produce a _copy_ or is it smart inasmuch as the compiler 
 will check if bar is actually mutated and so only create a 
 duplicate if needed?
If someone wants to modify something, they can go into assembly language and force it or use C wrappers that lie wouldn't stop it. Comes down to the programmers in the end.
 The motivation is that I want to have a class which contains a 
 dynamic array as a private member variable, but which is 
 readable through a class property.  I.e. something like

     class Foo
     {
         private int[] _foo;
          property immutable(int)[] foo { return _foo; }
     }

 ... but where it's guaranteed that no external agent can touch 
 the inside of _foo.  So, yes, I could return _foo.idup, but I'd 
 like to be able to guarantee that extra arrays won't be 
 allocated unless strictly necessary.
I would thing the returned property would be dup'ed (since immutable cannot be const, since const says 'I won't change' and immutable is 'I can't change') and that problem goes away. If you returned const(int)[], then it may return the array pointer but the calling function (and others) shouldn't be able to modify it. It almost sounds like you'd want const access with COW-like properties (copy-on-write), so if you try to make changes it duplicates it and uses the duplicate afterwards. I began a test for COW arrays before, but haven't finished it.
Aug 02 2012
parent Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
On 02/08/12 10:22, Era Scarecrow wrote:
   If someone wants to modify something, they can go into assembly language and
 force it or use C wrappers that lie wouldn't stop it. Comes down to the
 programmers in the end.
That's a very good point. I'm not asking for something rock-solid, but just whether there's something I can declare that the compiler will pick up on in the usual case. Though actually, I'm warming to the possibility to un-const an array via a cast; it would be something to use very cautiously, but potentially useful in places.
   It almost sounds like you'd want const access with COW-like properties
 (copy-on-write), so if you try to make changes it duplicates it and uses the
 duplicate afterwards. I began a test for COW arrays before, but haven't
finished
 it.
Pretty much; but I think Ali's notes on std.conv.to have probably provided me with the real toolset I need. Thanks very much for the advice!
Aug 02 2012
prev sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 08/02/2012 01:55 AM, Joseph Rushton Wakeling wrote:

 (i) is there a way to mark an array as "under no
 circumstances allow anything to modify the contents" in a way that
 _can't_ be cast away like this? and (ii) given that I can use instead
 bar = foo.dup to copy foo, is this guaranteed to produce a _copy_ or is
 it smart inasmuch as the compiler will check if bar is actually mutated
 and so only create a duplicate if needed?
.dup and .idup will always make a copy, std.conv.to won't: import std.conv; void main() { immutable(int)[] i = [ 1, 2 ]; immutable(int)[] j = to!(immutable(int)[])(i); assert(i is j); }
 The motivation is that I want to have a class which contains a dynamic
 array as a private member variable, but which is readable through a
 class property. i.e. something like

 class Foo
 {
 private int[] _foo;
  property immutable(int)[] foo { return _foo; }
 }
'const' is a better choice there: property const(int)[] foo() { return _foo; } You don't want to give the misleading promise that the data is immutable, as the caller may treat it as such and store for future use or freely pass to others threads.
 ... but where it's guaranteed that no external agent can touch the
 inside of _foo.
An intermediate type can reduce the chances of that happening, but it is not possible to prevent every potentially bad access in a system language like D. Ali
Aug 02 2012
parent reply Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
On 02/08/12 10:25, Ali Çehreli wrote:
 .dup and .idup will always make a copy, std.conv.to won't:
Brilliant. You could actually say that my _real_ problem was how to copy const(int)[] to int[] without the cast, as it's the cast that brings the lack of safety. std.conv.to seems to fit the bill perfectly.
 'const' is a better choice there:

       property const(int)[] foo() { return _foo; }

 You don't want to give the misleading promise that the data is immutable, as
the
 caller may treat it as such and store for future use or freely pass to others
 threads.
Very good point. It's too easy for me to forget this key difference between const and immutable.
 An intermediate type can reduce the chances of that happening, but it is not
 possible to prevent every potentially bad access in a system language like D.
Sure. What I'm looking for is not some absolute and unachievable prohibition, but a safety-checking mechanism that would be flagged by the compiler. The real problem was that I couldn't just set bar = foo without bar being const, but didn't know how to do this without the cast, which violates safety. Since to!()() allows me to make that copy -- or is it copy-on-write? -- in a safe way, that seems a perfectly viable solution.
Aug 02 2012
next sibling parent "Adam D. Ruppe" <destructionator gmail.com> writes:
If a function is marked  safe, casting away immutable is
not allowed by the compiler.

But, you can't put this on the array. It needs to be on
the user's functions:

 safe void foo(immutable(ubyte)[] arr) {
    auto cheating = cast(ubyte[]) arr;
}

test2.d(2): Error: cast from immutable(ubyte)[] to ubyte[] not 
allowed in safe code
Aug 02 2012
prev sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Thursday, 2 August 2012 at 11:42:21 UTC, Joseph Rushton 
Wakeling wrote:
 Sure.  What I'm looking for is not some absolute and 
 unachievable prohibition, but a safety-checking mechanism that 
 would be flagged by the compiler.  The real problem was that I 
 couldn't just set bar = foo without bar being const, but didn't 
 know how to do this without the cast, which violates safety.

 Since to!()() allows me to make that copy -- or is it 
 copy-on-write? -- in a safe way, that seems a perfectly viable 
 solution.
to!()() should make a copy from immutable to non, and vice-verse. COW can be easy to implement in functions, like string processing where once you've decided to change it, you make a mutable copy, modify it, then cast it back to immutable returning the new string. http://dlang.org/memory.html#copy-on-write COW outside of a function (as a type) could be a little different; a single flag could handle that case if it should copy or not; But having a structure to handle it may more complex. If there is not yet a COW container, I'll begin working on one.
Aug 02 2012