www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Read-only property without property

reply Steven Schveighoffer <schveiguy yahoo.com> writes:
I wanted to bring this over from D.learn, because I've never seen this 
before, and it's an interesting solution to creating a property without 
much boilerplate.

So here it is:

class Foo
{
    union
    {
       private int _a; // accessible only in this module
       public const int a; // accessible from anywhere, but read only
    }
}

And it works now, probably has for a while.

Thoughts? This can easily be boilerplated in something like roprop!(int, 
"a")

I am really not sure what union does to compiler optimization or runtime 
concerns, if it has any significant drawbacks. From what I can tell, 
it's a valid solution.

Credit to Mark Schütz for the idea.

-Steve
Sep 26 2014
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/26/14, 12:47 PM, Steven Schveighoffer wrote:
 I wanted to bring this over from D.learn, because I've never seen this
 before, and it's an interesting solution to creating a property without
 much boilerplate.

 So here it is:

 class Foo
 {
     union
     {
        private int _a; // accessible only in this module
        public const int a; // accessible from anywhere, but read only
     }
 }

 And it works now, probably has for a while.

 Thoughts? This can easily be boilerplated in something like roprop!(int,
 "a")

 I am really not sure what union does to compiler optimization or runtime
 concerns, if it has any significant drawbacks. From what I can tell,
 it's a valid solution.

 Credit to Mark Schütz for the idea.
Very interesting!! -- Andrei
Sep 26 2014
prev sibling next sibling parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
On Friday, 26 September 2014 at 19:47:15 UTC, Steven 
Schveighoffer wrote:
 I wanted to bring this over from D.learn, because I've never 
 seen this before, and it's an interesting solution to creating 
 a property without much boilerplate.

 So here it is:

 class Foo
 {
    union
    {
       private int _a; // accessible only in this module
       public const int a; // accessible from anywhere, but read 
 only
    }
 }

 And it works now, probably has for a while.

 Thoughts? This can easily be boilerplated in something like 
 roprop!(int, "a")

 I am really not sure what union does to compiler optimization 
 or runtime concerns, if it has any significant drawbacks. From 
 what I can tell, it's a valid solution.

 Credit to Mark Schütz for the idea.

 -Steve
I've often thought of such approaches, but not used them for fear of causing some horrible undefined behaviour (whether intentional or via a compiler bug). Are my fears misplaced?
Sep 26 2014
parent ketmar via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Fri, 26 Sep 2014 22:09:12 +0000
John Colvin via Digitalmars-d <digitalmars-d puremagic.com> wrote:

 Are my fears misplaced?
it should not incur any problems. and if such code faults, it's a bug in compiler which must be fixed. if we'll start to use such things, possible bugs will be ironed out.
Sep 26 2014
prev sibling next sibling parent ketmar via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Fri, 26 Sep 2014 15:47:15 -0400
Steven Schveighoffer via Digitalmars-d <digitalmars-d puremagic.com>
wrote:

 Thoughts? This can easily be boilerplated in something like
 roprop!(int, "a")
interesting thing. yet i want third arg to roprop. ;-)
Sep 26 2014
prev sibling next sibling parent reply "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Fri, Sep 26, 2014 at 03:47:15PM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
 I wanted to bring this over from D.learn, because I've never seen this
 before, and it's an interesting solution to creating a property
 without much boilerplate.
 
 So here it is:
 
 class Foo
 {
    union
    {
       private int _a; // accessible only in this module
       public const int a; // accessible from anywhere, but read only
    }
 }
[...] Very clever use of union to mean "this variable has two interpretations: a public, read-only one, and a private, read-write one". I like it! Even ddocs for the const "aspect" of 'a' turn out exactly like a normal member! Does the compiler infer it as safe, though? T -- "No, John. I want formats that are actually useful, rather than over-featured megaliths that address all questions by piling on ridiculous internal links in forms which are hideously over-complex." -- Simon St. Laurent on xml-dev
Sep 26 2014
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 9/26/14 6:32 PM, H. S. Teoh via Digitalmars-d wrote:

 Does the compiler infer it as  safe, though?
Hm... good point, I'm not sure if unions are considered safe. But I think that would be a decent enhancement request if not. -Steve
Sep 26 2014
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/26/14, 4:32 PM, Steven Schveighoffer wrote:
 On 9/26/14 6:32 PM, H. S. Teoh via Digitalmars-d wrote:

 Does the compiler infer it as  safe, though?
Hm... good point, I'm not sure if unions are considered safe. But I think that would be a decent enhancement request if not.
I'd say certain unions may be deemed safe. -- Andrei
Sep 26 2014
next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Fri, Sep 26, 2014 at 05:35:20PM -0700, Andrei Alexandrescu via Digitalmars-d
wrote:
 On 9/26/14, 4:32 PM, Steven Schveighoffer wrote:
On 9/26/14 6:32 PM, H. S. Teoh via Digitalmars-d wrote:

Does the compiler infer it as  safe, though?
Hm... good point, I'm not sure if unions are considered safe. But I think that would be a decent enhancement request if not.
I'd say certain unions may be deemed safe. -- Andrei
Hmm. I tried playing with the idea of unions being safe if all members have the same unqualified type, but quickly ran into some nasty cases: union U { void delegate() system sysDg; void delegate() safe safeDg; } Is this union safe or not? Technically, it should be, since there's no possibility of getting an invalid pointer to delegate using U. However, it also breaks safe-ty if you assign U.sysDg and call U.safeDg. (This is currently accepted by the compiler, btw. In safe code.) And what of: union U { immutable int x; int y; } ? This one breaks the type system. It's certainly safe, but has other issues. (The compiler happily accepts this one, BTW.) I think bug reports are in order. :-) There's also the question of what to do with class references: class Base {} class Derived : Base {} union U { Base b; Derived d; // fortunately, the compiler rejects this } I'm sure there are many more such tricky corner cases that people can dream up if they only use their imagination a little. T -- "Maybe" is a strange word. When mom or dad says it it means "yes", but when my big brothers say it it means "no"! -- PJ jr.
Sep 26 2014
prev sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Fri, Sep 26, 2014 at 06:20:46PM -0700, H. S. Teoh via Digitalmars-d wrote:
[...]
 	union U {
 		void delegate()  system sysDg;
 		void delegate()  safe safeDg;
 	}
 
 Is this union  safe or not? Technically, it should be, since there's no
 possibility of getting an invalid pointer to delegate using U. However,
 it also breaks  safe-ty if you assign U.sysDg and call U.safeDg. (This
 is currently accepted by the compiler, btw. In  safe code.)
https://issues.dlang.org/show_bug.cgi?id=13536
 And what of:
 
 	union U {
 		immutable int x;
 		int y;
 	}
 
 ? This one breaks the type system. It's certainly  safe, but has other
 issues. (The compiler happily accepts this one, BTW.)
https://issues.dlang.org/show_bug.cgi?id=13537 T -- It is the quality rather than the quantity that matters. -- Lucius Annaeus Seneca
Sep 26 2014
prev sibling parent reply "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Fri, Sep 26, 2014 at 07:32:49PM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
 On 9/26/14 6:32 PM, H. S. Teoh via Digitalmars-d wrote:
 
Does the compiler infer it as  safe, though?
Hm... good point, I'm not sure if unions are considered safe. But I think that would be a decent enhancement request if not.
[...] union U { int* ptr; long i; } void main() safe { U u; u.i = 12345; *u.ptr = 54321; // this can't possibly be safe } How would the compiler decide which union operations are safe and which are not? T -- He who laughs last thinks slowest.
Sep 26 2014
next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 9/26/14 8:58 PM, H. S. Teoh via Digitalmars-d wrote:
 On Fri, Sep 26, 2014 at 07:32:49PM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
 On 9/26/14 6:32 PM, H. S. Teoh via Digitalmars-d wrote:

 Does the compiler infer it as  safe, though?
Hm... good point, I'm not sure if unions are considered safe. But I think that would be a decent enhancement request if not.
[...] union U { int* ptr; long i; } void main() safe { U u; u.i = 12345; *u.ptr = 54321; // this can't possibly be safe } How would the compiler decide which union operations are safe and which are not?
Well, if all unions members are of the same base type, and they only differ by const (and by that I mean, const combined with either mutable or immutable), then it should be safe. So generically, these are safe: union { T t1; const T t2; } union { immutable T t1; const T t2; } This is not: union { T t1; immutable T t2; } checking current rules... looks like safe code doesn't flag unions as unsafe anywhere. I was even able to dereference pointers that were union'd with ints. But really, I think the most useful safe rules would be: 1. A union of any number of non-reference types with the same mutability throughout is safe, even if the type varies. 2. A union of two non-reference types, in which one union member has some bytes defined as mutable, and another union member has those same bytes defined as immutable, is un- safe. Const union members will not disqualify the union. 3. A union of any number of pointer types which point at the same type, but vary only by const, are safe, unless at least one member is mutable and at least one member is immutable. 4. Everything else is un- safe This may break some code, but I think it would define good starting rules to allow this in safe code. -Steve
Sep 26 2014
next sibling parent reply "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Fri, Sep 26, 2014 at 09:18:28PM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
 On 9/26/14 8:58 PM, H. S. Teoh via Digitalmars-d wrote:
On Fri, Sep 26, 2014 at 07:32:49PM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
On 9/26/14 6:32 PM, H. S. Teoh via Digitalmars-d wrote:

Does the compiler infer it as  safe, though?
Hm... good point, I'm not sure if unions are considered safe. But I think that would be a decent enhancement request if not.
[...] union U { int* ptr; long i; } void main() safe { U u; u.i = 12345; *u.ptr = 54321; // this can't possibly be safe } How would the compiler decide which union operations are safe and which are not?
Well, if all unions members are of the same base type, and they only differ by const (and by that I mean, const combined with either mutable or immutable), then it should be safe. So generically, these are safe: union { T t1; const T t2; } union { immutable T t1; const T t2; } This is not: union { T t1; immutable T t2; } checking current rules... looks like safe code doesn't flag unions as unsafe anywhere. I was even able to dereference pointers that were union'd with ints. But really, I think the most useful safe rules would be: 1. A union of any number of non-reference types with the same mutability throughout is safe, even if the type varies. 2. A union of two non-reference types, in which one union member has some bytes defined as mutable, and another union member has those same bytes defined as immutable, is un- safe. Const union members will not disqualify the union. 3. A union of any number of pointer types which point at the same type, but vary only by const, are safe, unless at least one member is mutable and at least one member is immutable. 4. Everything else is un- safe This may break some code, but I think it would define good starting rules to allow this in safe code.
[...] Not a bad start. Though I do note that *declaring* an unsafe union (according to the above definitions) is currently allowed in safe code by the compiler, but attempts to access a union member that overlaps with a pointer is rejected. IOW, the compiler doesn't refuse definitions of potentially unsafe unions, as long as you don't actually try to do something unsafe with them. That might make unions more useful (they can be passed around in safe code as long as certain operations are avoided), but probably also trickier to implement correctly. T -- Music critic: "That's an imitation fugue!"
Sep 26 2014
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 9/26/14 9:26 PM, H. S. Teoh via Digitalmars-d wrote:

 Not a bad start. Though I do note that *declaring* an unsafe union
 (according to the above definitions) is currently allowed in  safe code
 by the compiler, but attempts to access a union member that overlaps
 with a pointer is rejected.
It makes sense that you can declare unsafe unions, because a declaration itself isn't safe, it's only code that is. But my attempts to test this haven't yielded an error. e.g.: class Foo { union { private int _a; public int *a; } void setA(int x) safe { *a = x;} } no complaints...
 IOW, the compiler doesn't refuse definitions
 of potentially unsafe unions, as long as you don't actually try to do
 something unsafe with them. That might make unions more useful (they can
 be passed around in  safe code as long as certain operations are
 avoided), but probably also trickier to implement correctly.
I think it *should* be that way, but I'm not convinced it is yet. -Steve
Sep 26 2014
parent reply "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Sat, Sep 27, 2014 at 12:43:19AM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
 On 9/26/14 9:26 PM, H. S. Teoh via Digitalmars-d wrote:
 
Not a bad start. Though I do note that *declaring* an unsafe union
(according to the above definitions) is currently allowed in  safe
code by the compiler, but attempts to access a union member that
overlaps with a pointer is rejected.
It makes sense that you can declare unsafe unions, because a declaration itself isn't safe, it's only code that is. But my attempts to test this haven't yielded an error. e.g.: class Foo { union { private int _a; public int *a; } void setA(int x) safe { *a = x;} } no complaints...
Argh, looks like another incompletely-implemented part of the compiler: void fun() safe { union U { int p; int* q; } U u; u.p++; // compiles u.q = null; // compiles *u.q++; // x.d(9): Error: field U.q cannot be accessed in safe code because it overlaps with a pointer } While it sorta makes sense in its own way that it's OK to mess around with the union as long as you don't actually try to dereference an overlapped pointer, the error message inspires no confidence that this was actually intentional. :-( On the contrary, it sounds like u.p++ and u.q = null was supposed to be rejected as well, but for some reason aren't.
IOW, the compiler doesn't refuse definitions of potentially unsafe
unions, as long as you don't actually try to do something unsafe with
them. That might make unions more useful (they can be passed around
in  safe code as long as certain operations are avoided), but
probably also trickier to implement correctly.
I think it *should* be that way, but I'm not convinced it is yet.
[...] Apparently I hit jackpot in attempting to try dereferencing an overlapped pointer first. All the other cases seem to be unimplemented! I can't decide whether or not this should be filed as a bug or an enhancement request, because currently we have the paradoxical situation where safe code isn't actually allowed to *dereference* an overlapped pointer, but it *can* stomp all over the pointer by writing garbage to overlapping fields so that system callers can totally screw themselves over when they dereference it! T -- Knowledge is that area of ignorance that we arrange and classify. -- Ambrose Bierce
Sep 26 2014
parent reply "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"H. S. Teoh via Digitalmars-d"  wrote in message 
news:mailman.1799.1411796077.5783.digitalmars-d puremagic.com...

 Argh, looks like another incompletely-implemented part of the compiler:

 void fun()  safe {
 union U {
 int  p;
 int* q;
 }
 U u;
 u.p++; // compiles
 u.q = null; // compiles
 *u.q++; // x.d(9): Error: field U.q cannot be accessed in  safe code 
 because > it overlaps with a pointer
 }
This looks perfectly fine to me. Consider if you replaced all uses of the union with multiple variables and explicit casts: int p; int* q; p++; // fine, of course q++; // also fine p = 0; q = null; // both of these are fine, because they don't read anything from the union. // worst case, p was the last value assigned to *(*cast(int**)&p)++; // The cast can't be safe Without unions, you can't create invalid pointers in safe code. With unions, you can, but you can't access them.
Sep 27 2014
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 9/27/14 3:45 AM, Daniel Murphy wrote:
 "H. S. Teoh via Digitalmars-d"  wrote in message
 news:mailman.1799.1411796077.5783.digitalmars-d puremagic.com...

 Argh, looks like another incompletely-implemented part of the compiler:

 void fun()  safe {
 union U {
 int  p;
 int* q;
 }
 U u;
 u.p++; // compiles
 u.q = null; // compiles
 *u.q++; // x.d(9): Error: field U.q cannot be accessed in  safe code
 because > it overlaps with a pointer
I think that this is equivalent to u.q++, since the ++ does not apply to the pointed-at variable, but at the pointer itself. Weird, my tests showed that this was allowed. More testing, I realize it's because it's a class variable. This code compiles and runs: class Foo { union { int x; int *p; } void foo() safe {*p = 5;} } void main() safe { auto f = new Foo; f.foo(); } Which is similar to this code that does not: void main() safe { union U { int x; int *p; } U u; *u.p = 5; // error }
 Without unions, you can't create invalid pointers in  safe code.
 With unions, you can, but you can't access them.
The above shows you can in some situations, but I think that is a bug. -Steve
Sep 27 2014
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/26/14, 6:18 PM, Steven Schveighoffer wrote:
 1. A union of any number of non-reference types with the same mutability
 throughout is  safe, even if the type varies.
 2. A union of two non-reference types, in which one union member has
 some bytes defined as mutable, and another union member has those same
 bytes defined as immutable, is un- safe. Const union members will not
 disqualify the union.
 3. A union of any number of pointer types which point at the same type,
 but vary only by const, are  safe, unless at least one member is mutable
 and at least one member is immutable.
 4. Everything else is un- safe

 This may break some code, but I think it would define good starting
 rules to allow this in  safe code.
Nice! We need a complete set of rules for this stuff. -- Andrei
Sep 26 2014
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/26/14, 5:58 PM, H. S. Teoh via Digitalmars-d wrote:
 On Fri, Sep 26, 2014 at 07:32:49PM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
 On 9/26/14 6:32 PM, H. S. Teoh via Digitalmars-d wrote:

 Does the compiler infer it as  safe, though?
Hm... good point, I'm not sure if unions are considered safe. But I think that would be a decent enhancement request if not.
[...] union U { int* ptr; long i; } void main() safe { U u; u.i = 12345; *u.ptr = 54321; // this can't possibly be safe } How would the compiler decide which union operations are safe and which are not?
Unions mixing pointers and integrals aren't safe. Those mixing e.g. a float and an int are. -- Andrei
Sep 26 2014
prev sibling next sibling parent reply "Cliff" <cliff.s.hudson gmail.com> writes:
On Friday, 26 September 2014 at 19:47:15 UTC, Steven 
Schveighoffer wrote:
 I wanted to bring this over from D.learn, because I've never 
 seen this before, and it's an interesting solution to creating 
 a property without much boilerplate.

 So here it is:

 class Foo
 {
    union
    {
       private int _a; // accessible only in this module
       public const int a; // accessible from anywhere, but read 
 only
    }
 }

 And it works now, probably has for a while.

 Thoughts? This can easily be boilerplated in something like 
 roprop!(int, "a")

 I am really not sure what union does to compiler optimization 
 or runtime concerns, if it has any significant drawbacks. From 
 what I can tell, it's a valid solution.

 Credit to Mark Schütz for the idea.

 -Steve
This is a clever syntax, but I can't say I particularly care for it since it aliases two names for the same location which differ only in their visibility, and this feels... wrong to me somehow. In C# this is a sufficiently common practice that the property syntax allows for it directly: class Foo { int A { get; private set; } } The compiler automatically creates a (hidden) backing property (this is an implementation detail of course), both internal and external customers use the same name, and there is no redundancy. If I were to compare the D way and the C# way, I would prefer to C# way for this trivial-property case. What I would NOT want is C#'s special handling of properties to go along with it - a D analog would preserve A's access methods and handling as if it were a field if that was the user's wish. That's my $0.02.
Sep 26 2014
parent "Kapps" <opantm2+spam gmail.com> writes:
On Friday, 26 September 2014 at 22:58:53 UTC, Cliff wrote:
 This is a clever syntax, but I can't say I particularly care 
 for it since it aliases two names for the same location which 
 differ only in their visibility, and this feels... wrong to me 
 somehow.

 In C# this is a sufficiently common practice that the property 
 syntax allows for it directly:

 class Foo
 {
     int A { get; private set; }
 }

 The compiler automatically creates a (hidden) backing property 
 (this is an implementation detail of course), both internal and 
 external customers use the same name, and there is no 
 redundancy.
  If I were to compare the D way and the C# way, I would prefer 
 to C# way for this trivial-property case.  What I would NOT 
 want is C#'s special handling of properties to go along with it 
 - a D analog would preserve A's access methods and handling as 
 if it were a field if that was the user's wish.

 That's my $0.02.
C#'s way of declaring properties is something I've been missing in D for a long time. Using a mixin is just plain ugly and breaks tooling, yet this is such a common thing to have to do. It is quite disappointing that there's no shorthand for it, like ' property int a;' or ' property(readonly) int b'. (On that matter, I'm not a fan in general of properties being various methods glued together by an property, but that's set in stone by now.)
Sep 26 2014
prev sibling parent "Daniel N" <ufo orbiting.us> writes:
On Friday, 26 September 2014 at 19:47:15 UTC, Steven 
Schveighoffer wrote:
 I wanted to bring this over from D.learn, because I've never 
 seen this before, and it's an interesting solution to creating 
 a property without much boilerplate.

 So here it is:

 class Foo
 {
    union
    {
       private int _a; // accessible only in this module
       public const int a; // accessible from anywhere, but read 
 only
    }
 }

 And it works now, probably has for a while.

 Thoughts? This can easily be boilerplated in something like 
 roprop!(int, "a")

 I am really not sure what union does to compiler optimization 
 or runtime concerns, if it has any significant drawbacks. From 
 what I can tell, it's a valid solution.

 Credit to Mark Schütz for the idea.

 -Steve
Thanks, I love this! There is actually a very important ABI difference, in all other "property" implementations in other languages(of which I'm aware), the ABI is changed to be a method rather than a member, for this reason I think we shouldn't refer to this feature as a "property", maybe "field/member" is more appropriate? Both properties and "access restricted fields" has their place depending on what one is trying to accomplish, but in most cases I find myself wanting this very feature and not normal properties. I couldn't resist trying this in C++, clang rejected it outright, GCC let it through with a warning.
Sep 26 2014