digitalmars.D - this() not executing code on structs
- Andrei Alexandrescu (39/39) Oct 21 2009 Today, structs can't write their own this(). There aren't very solid
- zoli (6/10) Oct 21 2009 If the new "operator" for the class is redesigned to have multiply alloc...
- dsimcha (14/22) Oct 21 2009 classes instead of structs for small objects( like vec2, vec3) as well.
- Denis Koroskin (62/101) Oct 21 2009 I agree it's a very annoying limitation, but I believe there is a
- Bartosz Milewski (7/8) Oct 21 2009 It's worse than that. Try this:
- Leandro Lucarella (10/24) Oct 21 2009 Fill bug reports in bugzilla, please!
- Don (4/17) Oct 23 2009 Ouch.
- Andrei Alexandrescu (6/25) Oct 23 2009 http://d.puremagic.com/issues/show_bug.cgi?id=3438
- Denis Koroskin (5/29) Oct 23 2009 I'd suggest ditching it and enforce explicit member initialization (unle...
- Andrei Alexandrescu (11/45) Oct 23 2009 I don't understand. The problem right now is that even of all fields are...
- Denis Koroskin (25/69) Oct 23 2009 Yes, I was talking about a different scheme, where default ctors are
- Denis Koroskin (16/90) Oct 23 2009 This scheme works fine for C++, and it would fit languages that don't
- Bartosz Milewski (2/2) Oct 21 2009 This means that no non-trivial invariant may be defined for a struct. In...
- Rainer Deyke (10/54) Oct 21 2009 Any struct that uses dynamic memory allocation internally.
- grauzone (17/62) Oct 21 2009 I'd really like to know why "scope x = new X();" is "unsafe", while
- Steven Schveighoffer (9/16) Oct 21 2009 A class is a reference type. If you pass the reference to the stack to ...
- dsimcha (22/32) Oct 21 2009 Because classes in D are always passed by pointer. (Technically referen...
- grauzone (13/49) Oct 22 2009 (I'm talking about scope classes as declared in "scope class T { ... }")
- Andrei Alexandrescu (16/46) Oct 22 2009 I wish it was as easy as it sounds. In fact you don't need to pass scope...
- Denis Koroskin (4/50) Oct 22 2009 Scope classes could be disallowed in SafeD, but you can't disallow
- grauzone (26/77) Oct 22 2009 OK, but the user of that class is still forced to use it safely. It's
- Andrei Alexandrescu (6/92) Oct 22 2009 It's right to the point. I keep on forgetting about that corner case,
- Steven Schveighoffer (22/56) Oct 23 2009 Your original question was about the statement "scope x = new X()", whic...
- grauzone (22/91) Oct 23 2009 You can end up passing hidden pointers quickly, because a struct's this
Today, structs can't write their own this(). There aren't very solid reasons for that except that it makes language implementation more difficult. I wonder how much of a problem that could be in practice. I realized today that the "Counted" example - a classic C++ primer example featuring a struct that counts its own instances - cannot be implemented in D. In C++ the counted example looks like this: struct Counted { static unsigned count; unsigned myCount; Counted() { myCount = count++; } Counted(const Counted& rhs) { myCount = count++; } Counted& operator=(const Counted& rhs) { // no writing to myCount return *this; } ~Counted() { --count; } } In D there's no chance to write Counted because you can always create Counted objects without executing any code. struct Counted { static uint count; uint myCount; this() { myCount = count++; } // ERROR this(this) { myCount = count++; } ref Counted opAssign(Counted rhs) { // no writing to myCount return this; } ~this() { --count; } } This being a toy example, I wonder whether there are much more serious examples that would be impossible to implement within D. Andrei
Oct 21 2009
Andrei Alexandrescu Wrote:Today, structs can't write their own this(). There aren't very solid reasons for that except that it makes language implementation more difficult.If the new "operator" for the class is redesigned to have multiply allocation types (allocate on heap, allocate on stack, etc.?), there is no main drawback of using classes instead of structs for small objects( like vec2, vec3) as well. For function parameters, use the reference for in/out and, use a copy of the original for value parameters ( personally I hate codes where they change the input parameters when they are value types - for larger codes it's hard to track what's going on in the function, and whether it's the original or the modified value...) I don't think if there's any reason to use struct with member functions from that point, use class instead. And hence struct can be the good old plain data structures again, to give some semantics to a block of memory. But if the scoped allocation is gone, the struct will require much more construction powers, and will be used more (ex vec2, vec3, etc.)
Oct 21 2009
== Quote from zoli (zoli freemail.hu)'s articleAndrei Alexandrescu Wrote:classes instead of structs for small objects( like vec2, vec3) as well. Yes there is. How about the cost of storing vtbl and monitor? Actually, just last night I was in a corner case where this mattered (as well as needing fine control over allocation), so I ended up rolling my own polymorphism with structs. This is not a complaint about the design of D classes. They do exactly what they should do: Handle the first 99% of use cases well and make people in obscure corner cases roll their own from lower level primitives rather than succumbing to the inner platform effect. However, I think it would be ridiculous not to allow simple syntactic sugar like non-polymorphic member functions on structs.Today, structs can't write their own this(). There aren't very solid reasons for that except that it makes language implementation more difficult.If the new "operator" for the class is redesigned to have multiply allocation types (allocate on heap, allocate on stack, etc.?), there is no main drawback of usingI don't think if there's any reason to use struct with member functions fromthat point, use class instead. And hence struct can be the good old plain data structures again, to give some semantics to a block of memory. Ok, simple example that's not a corner case: How about storing one inline in an array?
Oct 21 2009
I keep forgetting those (4-8) extra bytes per instance :) And what about the final classes (with no ancestors)? Is there a vtbl for them since they cannot be derived and they have no virtual parent to inherit members from ? Or the Object with opHash and the other operators are virtual by nature in all classes ? (sorry, I don't know D that much) Well, actually it seems as struct is a much better and clear solution. In that case I vote for this().If the new "operator" for the class is redesigned to have multiply allocation types (allocate on heap, allocate on stack, etc.?), there is no main drawback of usingclasses instead of structs for small objects( like vec2, vec3) as well. Yes there is. How about the cost of storing vtbl and monitor? Actually, just
Oct 21 2009
== Quote from zoli (galap freemail.hu)'s articleallocation typesIf the new "operator" for the class is redesigned to have multiplyEven final classes w/ no explicit ancestors are implicitly subclasses of Object, which defines virtual functions. Therefore, *all* class instances must have a vtbl pointer.I keep forgetting those (4-8) extra bytes per instance :) And what about the final classes (with no ancestors)?(allocate on heap, allocate on stack, etc.?), there is no main drawback of usingclasses instead of structs for small objects( like vec2, vec3) as well. Yes there is. How about the cost of storing vtbl and monitor? Actually, just
Oct 21 2009
On Wed, 21 Oct 2009 20:15:16 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Today, structs can't write their own this(). There aren't very solid reasons for that except that it makes language implementation more difficult. I wonder how much of a problem that could be in practice. I realized today that the "Counted" example - a classic C++ primer example featuring a struct that counts its own instances - cannot be implemented in D. In C++ the counted example looks like this: struct Counted { static unsigned count; unsigned myCount; Counted() { myCount = count++; } Counted(const Counted& rhs) { myCount = count++; } Counted& operator=(const Counted& rhs) { // no writing to myCount return *this; } ~Counted() { --count; } } In D there's no chance to write Counted because you can always create Counted objects without executing any code. struct Counted { static uint count; uint myCount; this() { myCount = count++; } // ERROR this(this) { myCount = count++; } ref Counted opAssign(Counted rhs) { // no writing to myCount return this; } ~this() { --count; } } This being a toy example, I wonder whether there are much more serious examples that would be impossible to implement within D. AndreiI agree it's a very annoying limitation, but I believe there is a rationale behind it. Imagine a class that aggregates a struct: struct Foo { ... } class Bar { Foo foo; this() {} // ... } The class object construction is now consists of two steps: 1) memcpy the Bar.classinfo.init 2) call __ctor() Unlike C++, there is no stage at which class members are being initialized, and it simplifies things quite a lot. Think about the following: what happens if structs will be allowed default ctors? How to avoid double initialization? You may end up with design very similar to C++ in this case (including an initialization list). Some problems could be solved with an enforced explicit initialization of each member. That's the only way I see now that would avoid double initialization of a struct in presence of default and non-default ctors: struct Scoped(T) // I like this name a lot more than InPlace, and especially InSitu { this(Args...)(Args args) { T obj = cast(T)data.ptr; obj.__ctor(args); } ubyte[T.classinfo.init.length] data = T.classinfo.init; // I believe this should work at compile-time, since classinfo is immutable } class Foo { this() {} this(int i) {} } class Bar { Scoped!(Foo) foo; this() { // foo is already constructed by now (default ctor is called) which is cool // but what if I need to initialize it with some other ctor? foo = Scoped!(Foo)(42); // double initialization } int doesntNeedToBeInitializedExplicitlyInACtor = 17; } Enforcing explicit initialization of each member (unless its value is set at the declaration, as in the example above) is gracefully solving this issue. Also think about exception safety: what if an exception is thrown in a class ctor - should the dtors be invoked on initialized members or not? If yes, in what order? D doesn't enforce initialization order, so it's a bit tricky to determine what members are already initialized and need to be destroyed (with a dtor call) efficiently. C++ handles issues like this very well IMO. I believe D should also have a sound solution to this problem.
Oct 21 2009
Andrei Alexandrescu Wrote:this() { myCount = count++; } // ERRORIt's worse than that. Try this: struct foo { this(int dummy = 0) { writeln("Default constructor");} } foo x = foo(); Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
Oct 21 2009
Bartosz Milewski, el 21 de octubre a las 16:33 me escribiste:Andrei Alexandrescu Wrote:Fill bug reports in bugzilla, please! -- Leandro Lucarella (AKA luca) http://llucax.com.ar/ ---------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------- Los jóvenes no son solo brazos que nos cargan... También se los puede mandar a la guerra, que es su obligación. -- Ricardo Vaporesothis() { myCount = count++; } // ERRORIt's worse than that. Try this: struct foo { this(int dummy = 0) { writeln("Default constructor");} } foo x = foo(); Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
Oct 21 2009
Bartosz Milewski wrote:Andrei Alexandrescu Wrote:Ouch. It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.this() { myCount = count++; } // ERRORIt's worse than that. Try this: struct foo { this(int dummy = 0) { writeln("Default constructor");} } foo x = foo(); Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
Oct 23 2009
Don wrote:Bartosz Milewski wrote:http://d.puremagic.com/issues/show_bug.cgi?id=3438 The more I think of it, the more imperious it becomes that we allow default constructors that execute code. The main question is what to do about .init. AndreiAndrei Alexandrescu Wrote:Ouch. It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.this() { myCount = count++; } // ERRORIt's worse than that. Try this: struct foo { this(int dummy = 0) { writeln("Default constructor");} } foo x = foo(); Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
Oct 23 2009
On Fri, 23 Oct 2009 18:46:47 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Don wrote:I'd suggest ditching it and enforce explicit member initialization (unless a variable is nullable). This will also remove a lot of bloat from executables.Bartosz Milewski wrote:http://d.puremagic.com/issues/show_bug.cgi?id=3438 The more I think of it, the more imperious it becomes that we allow default constructors that execute code. The main question is what to do about .init. AndreiAndrei Alexandrescu Wrote:Ouch. It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.this() { myCount = count++; } // ERRORIt's worse than that. Try this: struct foo { this(int dummy = 0) { writeln("Default constructor");} } foo x = foo(); Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
Oct 23 2009
Denis Koroskin wrote:On Fri, 23 Oct 2009 18:46:47 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I don't understand. The problem right now is that even of all fields are explicitly intialized, e.g. struct A { Widget x = null; double d = 0; int i = -1; } there is still no ability to execute code upon initialization, e.g. force A to contain a non-null Widget. AndreiDon wrote:I'd suggest ditching it and enforce explicit member initialization (unless a variable is nullable). This will also remove a lot of bloat from executables.Bartosz Milewski wrote:http://d.puremagic.com/issues/show_bug.cgi?id=3438 The more I think of it, the more imperious it becomes that we allow default constructors that execute code. The main question is what to do about .init. AndreiAndrei Alexandrescu Wrote:Ouch. It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.this() { myCount = count++; } // ERRORIt's worse than that. Try this: struct foo { this(int dummy = 0) { writeln("Default constructor");} } foo x = foo(); Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
Oct 23 2009
On Fri, 23 Oct 2009 19:40:33 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Denis Koroskin wrote:Yes, I was talking about a different scheme, where default ctors are allowed. They aren't allowed now but we are talking about things we can change/improve, right? I mean there is no _real_ need for T.init, just malloc()'ate some memory and call the __ctor on it. Struct members default values would be converted into runtime initialization expressions like this: struct A { this(Args)(Args args) // may be an empty set of arguments { // the following lines are inserted automatically x = null; d = 0; i = -1; // user-defined code follows i = 42; } Widget x = null; double d = 0; int i = -1; } Optimization pass would eliminate double initialization (in the case about i would be initialized straight to 42)On Fri, 23 Oct 2009 18:46:47 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I don't understand. The problem right now is that even of all fields are explicitly intialized, e.g. struct A { Widget x = null; double d = 0; int i = -1; } there is still no ability to execute code upon initialization, e.g. force A to contain a non-null Widget. AndreiDon wrote:I'd suggest ditching it and enforce explicit member initialization (unless a variable is nullable). This will also remove a lot of bloat from executables.Bartosz Milewski wrote:http://d.puremagic.com/issues/show_bug.cgi?id=3438 The more I think of it, the more imperious it becomes that we allow default constructors that execute code. The main question is what to do about .init. AndreiAndrei Alexandrescu Wrote:Ouch. It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.this() { myCount = count++; } // ERRORIt's worse than that. Try this: struct foo { this(int dummy = 0) { writeln("Default constructor");} } foo x = foo(); Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
Oct 23 2009
On Sat, 24 Oct 2009 03:28:02 +0400, Denis Koroskin <2korden gmail.com> wrote:On Fri, 23 Oct 2009 19:40:33 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:This scheme works fine for C++, and it would fit languages that don't support MI even better. I believe D's approach with init is a bit easier to understand (rules are less complicated), but it is also not very efficient: a few stores/pushes are faster than memcpy'ing few bytes in most cases (minor but still). And an additional bloat it introduces is also not very welcome, of course. While I'm not a idealizing C++ object initialization scheme, I do think it is sound (even though it is complicated). Some variation of it may be well suitable for D. I made the following post a while ago (http://www.digitalmars.com/d/archives/digitalmars/D/D_programming_practices_object_constructio _order_85468.html), it may interest you as I believe it is relevant to this discussion. Unfortunately no one has made any comments about it (is it silly, or just nobody cares?).Denis Koroskin wrote:Yes, I was talking about a different scheme, where default ctors are allowed. They aren't allowed now but we are talking about things we can change/improve, right? I mean there is no _real_ need for T.init, just malloc()'ate some memory and call the __ctor on it. Struct members default values would be converted into runtime initialization expressions like this: struct A { this(Args)(Args args) // may be an empty set of arguments { // the following lines are inserted automatically x = null; d = 0; i = -1; // user-defined code follows i = 42; } Widget x = null; double d = 0; int i = -1; } Optimization pass would eliminate double initialization (in the case about i would be initialized straight to 42)On Fri, 23 Oct 2009 18:46:47 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I don't understand. The problem right now is that even of all fields are explicitly intialized, e.g. struct A { Widget x = null; double d = 0; int i = -1; } there is still no ability to execute code upon initialization, e.g. force A to contain a non-null Widget. AndreiDon wrote:I'd suggest ditching it and enforce explicit member initialization (unless a variable is nullable). This will also remove a lot of bloat from executables.Bartosz Milewski wrote:http://d.puremagic.com/issues/show_bug.cgi?id=3438 The more I think of it, the more imperious it becomes that we allow default constructors that execute code. The main question is what to do about .init. AndreiAndrei Alexandrescu Wrote:Ouch. It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.this() { myCount = count++; } // ERRORIt's worse than that. Try this: struct foo { this(int dummy = 0) { writeln("Default constructor");} } foo x = foo(); Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
Oct 23 2009
This means that no non-trivial invariant may be defined for a struct. In most cases it doesn't matter, but it might be a problem with the smart pointer family. For instance, refcounted may not assume that the shared refcount pointer has been allocated. Without this invariant all refcounted operations must have additional code that tests for null and does the right thing (which is not always obvious).
Oct 21 2009
Andrei Alexandrescu wrote:Today, structs can't write their own this(). There aren't very solid reasons for that except that it makes language implementation more difficult. I wonder how much of a problem that could be in practice. I realized today that the "Counted" example - a classic C++ primer example featuring a struct that counts its own instances - cannot be implemented in D. In C++ the counted example looks like this: struct Counted { static unsigned count; unsigned myCount; Counted() { myCount = count++; } Counted(const Counted& rhs) { myCount = count++; } Counted& operator=(const Counted& rhs) { // no writing to myCount return *this; } ~Counted() { --count; } } In D there's no chance to write Counted because you can always create Counted objects without executing any code. struct Counted { static uint count; uint myCount; this() { myCount = count++; } // ERROR this(this) { myCount = count++; } ref Counted opAssign(Counted rhs) { // no writing to myCount return this; } ~this() { --count; } } This being a toy example, I wonder whether there are much more serious examples that would be impossible to implement within D.Any struct that uses dynamic memory allocation internally. Any struct that registers its instances in some dort of global registry. 'ValueType!T', which turns reference type 'T' into a value type. A 'ScopedLock' variant that uses a single global mutex. RAII wrappers over global initialization/deinitialization functions. A 'UniqueId' struct that initializes to a value that is guaranteed to be distinct from the vale of any other 'UniqueId' used by the program. -- Rainer Deyke - rainerd eldwood.com
Oct 21 2009
Andrei Alexandrescu wrote:Today, structs can't write their own this(). There aren't very solid reasons for that except that it makes language implementation more difficult. I wonder how much of a problem that could be in practice. I realized today that the "Counted" example - a classic C++ primer example featuring a struct that counts its own instances - cannot be implemented in D. In C++ the counted example looks like this: struct Counted { static unsigned count; unsigned myCount; Counted() { myCount = count++; } Counted(const Counted& rhs) { myCount = count++; } Counted& operator=(const Counted& rhs) { // no writing to myCount return *this; } ~Counted() { --count; } } In D there's no chance to write Counted because you can always create Counted objects without executing any code. struct Counted { static uint count; uint myCount; this() { myCount = count++; } // ERROR this(this) { myCount = count++; } ref Counted opAssign(Counted rhs) { // no writing to myCount return this; } ~this() { --count; } } This being a toy example, I wonder whether there are much more serious examples that would be impossible to implement within D.I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong. Why do you want to add class functionality to structs to enable "RAII" like features, when you could just use scope classes? To refresh everyone's memory: a scope class is declared like "scope class Foo { ... }", and references to it can "only appear as a function local variable". I for one don't really like the idea of having to distinguish between PODs and "other stuff" (like you had in C++) just again. At the very least, the default constructor should always be available for structs. (If not, have fun figuring out what S[10] should do.)Andrei
Oct 21 2009
On Wed, 21 Oct 2009 17:54:08 -0400, grauzone <none example.net> wrote:I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.A class is a reference type. If you pass the reference to the stack to a function that then stores it for later use, it is unsafe. If you return it from the function, it's unsafe. If you do the same with a struct (not a struct pointer), this is not the case. Note that you *could* have the same problem with struct pointers or references, but typically, you expect to treat struct references like they are referencing stack data, it's not typical for classes. -Steve
Oct 21 2009
== Quote from grauzone (none example.net)'s articleAndrei Alexandrescu wrote: I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.Because classes in D are always passed by pointer. (Technically references, but really they're just pointers under the hood.) Returning a scope (stack-allocated) class from a function is equivalent to escaping a pointer to a stack variable. Returning a struct is done by value, just like returning an int. Classes can't be value types because they are polymorphic, meaning their size isn't known at compile time. C++ tries to make them value types but really, there is no *good* way to make a polymorphic type with size not known at compile time a value type.Why do you want to add class functionality to structs to enable "RAII" like features, when you could just use scope classes?To me, this makes perfect sense. Classes are polymorphic, structs are value types. Except in the "here be dragons" world of C++, the two are mutually exclusive, which is the reason for the dichotomy in the first place. Therefore, structs should do everything they can w/o being polymorphic and classes should do everything they can w/o being value types. You then decide which one you want based on whether you need value semantics or polymorphism more. The only place in D where this logic breaks down is monitor objects on classes. Even here, while structs technically *could* be given monitors, this is inefficient because they are value types, whereas the efficiency loss from storing a few extra bytes in a reference type is minimal in most cases. Scope is really a dangerous hack to allocate a *reference type* on the stack. It's dangerous and kludgey, but in a performance-oriented language it's a necessary evil.
Oct 21 2009
dsimcha wrote:== Quote from grauzone (none example.net)'s article(I'm talking about scope classes as declared in "scope class T { ... }") But you can't return scope classes from a function. You can't pass them as ref parameters either. They're designed to be safe. On the other hand, you can pass struct pointers all the way you want around, and it's damn unsafe. I don't get this "structs are safe because they are value types" argument anyway, because the this pointer for structs is a pointer/reference anyway. If it's trivial to break that "safety", can you really call it "safety"?Andrei Alexandrescu wrote: I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.Because classes in D are always passed by pointer. (Technically references, but really they're just pointers under the hood.) Returning a scope (stack-allocated) class from a function is equivalent to escaping a pointer to a stack variable. Returning a struct is done by value, just like returning an int.Classes can't be value types because they are polymorphic, meaning their size isn't known at compile time. C++ tries to make them value types but really, there is no *good* way to make a polymorphic type with size not known at compile time a value type.Why do all objects have monitor pointers anyway? The idea, that every object can act as lock, is a really bad one. But this is off-topic...Why do you want to add class functionality to structs to enable "RAII" like features, when you could just use scope classes?To me, this makes perfect sense. Classes are polymorphic, structs are value types. Except in the "here be dragons" world of C++, the two are mutually exclusive, which is the reason for the dichotomy in the first place. Therefore, structs should do everything they can w/o being polymorphic and classes should do everything they can w/o being value types. You then decide which one you want based on whether you need value semantics or polymorphism more. The only place in D where this logic breaks down is monitor objects on classes. Even here, while structs technically *could* be given monitors, this is inefficient because they are value types, whereas the efficiency loss from storing a few extra bytes in a reference type is minimal in most cases.Scope is really a dangerous hack to allocate a *reference type* on the stack. It's dangerous and kludgey, but in a performance-oriented language it's a necessary evil.You could say the same about structs.
Oct 22 2009
grauzone wrote:dsimcha wrote:I wish it was as easy as it sounds. In fact you don't need to pass scope classes as ref parameters - it's enough to pass them "by value" because they are implicitly references. You can't even safely call a method on a scope class object because that method may assign "this" to something escaping the scope of the method. Save for using some flavor of interprocedural escape analysis and/or making "scope" a function attribute, I'm not seeing how scope can be made safe and reasonably useful.== Quote from grauzone (none example.net)'s article(I'm talking about scope classes as declared in "scope class T { ... }") But you can't return scope classes from a function. You can't pass them as ref parameters either. They're designed to be safe.Andrei Alexandrescu wrote: I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.Because classes in D are always passed by pointer. (Technically references, but really they're just pointers under the hood.) Returning a scope (stack-allocated) class from a function is equivalent to escaping a pointer to a stack variable. Returning a struct is done by value, just like returning an int.On the other hand, you can pass struct pointers all the way you want around, and it's damn unsafe. I don't get this "structs are safe because they are value types" argument anyway, because the this pointer for structs is a pointer/reference anyway. If it's trivial to break that "safety", can you really call it "safety"?The point is that you can disable address taking altogether and still write a great deal of good code in D. If address taking is verboten (e.g. in SafeD), ref parameters can never be escaped (they will be scoped) and therefore they become safe, too. So within SafeD, structs become safe, but scope class objects still couldn't be made safe without heroic effort. Andrei
Oct 22 2009
On Thu, 22 Oct 2009 21:01:15 +0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:grauzone wrote:Scope classes could be disallowed in SafeD, but you can't disallow Scope!(Object).dsimcha wrote:I wish it was as easy as it sounds. In fact you don't need to pass scope classes as ref parameters - it's enough to pass them "by value" because they are implicitly references. You can't even safely call a method on a scope class object because that method may assign "this" to something escaping the scope of the method. Save for using some flavor of interprocedural escape analysis and/or making "scope" a function attribute, I'm not seeing how scope can be made safe and reasonably useful.== Quote from grauzone (none example.net)'s article(I'm talking about scope classes as declared in "scope class T { ... }") But you can't return scope classes from a function. You can't pass them as ref parameters either. They're designed to be safe.Andrei Alexandrescu wrote: I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.Because classes in D are always passed by pointer. (Technically references, but really they're just pointers under the hood.) Returning a scope (stack-allocated) class from a function is equivalent to escaping a pointer to a stack variable. Returning a struct is done by value, just like returning an int.On the other hand, you can pass struct pointers all the way you want around, and it's damn unsafe. I don't get this "structs are safe because they are value types" argument anyway, because the this pointer for structs is a pointer/reference anyway. If it's trivial to break that "safety", can you really call it "safety"?The point is that you can disable address taking altogether and still write a great deal of good code in D. If address taking is verboten (e.g. in SafeD), ref parameters can never be escaped (they will be scoped) and therefore they become safe, too. So within SafeD, structs become safe, but scope class objects still couldn't be made safe without heroic effort. Andrei
Oct 22 2009
Andrei Alexandrescu wrote:grauzone wrote:OK, but the user of that class is still forced to use it safely. It's the implementor's responsibility to make sure he doesn't to unsafe things. It's the same with structs, at least in normal D (not SafeD).dsimcha wrote:I wish it was as easy as it sounds. In fact you don't need to pass scope classes as ref parameters - it's enough to pass them "by value" because they are implicitly references. You can't even safely call a method on a scope class object because that method may assign "this" to something escaping the scope of the method. Save for using some flavor of interprocedural escape analysis and/or making "scope" a function attribute, I'm not seeing how scope can be made safe and reasonably useful.== Quote from grauzone (none example.net)'s article(I'm talking about scope classes as declared in "scope class T { ... }") But you can't return scope classes from a function. You can't pass them as ref parameters either. They're designed to be safe.Andrei Alexandrescu wrote: I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.Because classes in D are always passed by pointer. (Technically references, but really they're just pointers under the hood.) Returning a scope (stack-allocated) class from a function is equivalent to escaping a pointer to a stack variable. Returning a struct is done by value, just like returning an int.I wonder about that. How will the following example will be made safe? Right now, ref parameters _can_ escape with the help of ref returns. import std.stdio; ref int foo(ref int z, int something) { return z; } ref int foo2() { int u = 123; return foo(u, 0); } void main() { writefln("%s", foo(foo2(), {char[20] tmp; return 0; }())); } It's probably not minimal, but it demonstrates how you can return references to outdated stack locations without explicitly taking pointers. That delegate literal overwrites the stack, so that writefln outputs garbage. How to solve this? Disallow ref arguments or ref returns in SafeD, because they are hidden "address taking"? Or is this all besides the point? (Hello world doesn't compile with -safe (Phobos issue), so I couldn't test it with -safe on dmd v2.032.)On the other hand, you can pass struct pointers all the way you want around, and it's damn unsafe. I don't get this "structs are safe because they are value types" argument anyway, because the this pointer for structs is a pointer/reference anyway. If it's trivial to break that "safety", can you really call it "safety"?The point is that you can disable address taking altogether and still write a great deal of good code in D. If address taking is verboten (e.g. in SafeD), ref parameters can never be escaped (they will be scoped) and therefore they become safe, too. So within SafeD, structs become safe, but scope class objects still couldn't be made safe without heroic effort.Andrei
Oct 22 2009
grauzone wrote:Andrei Alexandrescu wrote:It's right to the point. I keep on forgetting about that corner case, Steve pointed it to me a long time ago. In the call foo(u, 0) the compiler must conservatively assume that the scope of the returned value is the same as the scope of u, and therefore disallow compilation of foo2. Andreigrauzone wrote:OK, but the user of that class is still forced to use it safely. It's the implementor's responsibility to make sure he doesn't to unsafe things. It's the same with structs, at least in normal D (not SafeD).dsimcha wrote:I wish it was as easy as it sounds. In fact you don't need to pass scope classes as ref parameters - it's enough to pass them "by value" because they are implicitly references. You can't even safely call a method on a scope class object because that method may assign "this" to something escaping the scope of the method. Save for using some flavor of interprocedural escape analysis and/or making "scope" a function attribute, I'm not seeing how scope can be made safe and reasonably useful.== Quote from grauzone (none example.net)'s article(I'm talking about scope classes as declared in "scope class T { ... }") But you can't return scope classes from a function. You can't pass them as ref parameters either. They're designed to be safe.Andrei Alexandrescu wrote: I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.Because classes in D are always passed by pointer. (Technically references, but really they're just pointers under the hood.) Returning a scope (stack-allocated) class from a function is equivalent to escaping a pointer to a stack variable. Returning a struct is done by value, just like returning an int.I wonder about that. How will the following example will be made safe? Right now, ref parameters _can_ escape with the help of ref returns. import std.stdio; ref int foo(ref int z, int something) { return z; } ref int foo2() { int u = 123; return foo(u, 0); } void main() { writefln("%s", foo(foo2(), {char[20] tmp; return 0; }())); } It's probably not minimal, but it demonstrates how you can return references to outdated stack locations without explicitly taking pointers. That delegate literal overwrites the stack, so that writefln outputs garbage. How to solve this? Disallow ref arguments or ref returns in SafeD, because they are hidden "address taking"? Or is this all besides the point?On the other hand, you can pass struct pointers all the way you want around, and it's damn unsafe. I don't get this "structs are safe because they are value types" argument anyway, because the this pointer for structs is a pointer/reference anyway. If it's trivial to break that "safety", can you really call it "safety"?The point is that you can disable address taking altogether and still write a great deal of good code in D. If address taking is verboten (e.g. in SafeD), ref parameters can never be escaped (they will be scoped) and therefore they become safe, too. So within SafeD, structs become safe, but scope class objects still couldn't be made safe without heroic effort.
Oct 22 2009
On Thu, 22 Oct 2009 12:50:21 -0400, grauzone <none example.net> wrote:dsimcha wrote:Your original question was about the statement "scope x = new X()", which can be done on any type of class, even non-scope ones.== Quote from grauzone (none example.net)'s article(I'm talking about scope classes as declared in "scope class T { ... }")Andrei Alexandrescu wrote: I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.Because classes in D are always passed by pointer. (Technically references, but really they're just pointers under the hood.) Returning a scope (stack-allocated) class from a function is equivalent to escaping a pointer to a stack variable. Returning a struct is done by value, just like returning an int.But you can't return scope classes from a function. You can't pass them as ref parameters either. They're designed to be safe. On the other hand, you can pass struct pointers all the way you want around, and it's damn unsafe. I don't get this "structs are safe because they are value types" argument anyway, because the this pointer for structs is a pointer/reference anyway. If it's trivial to break that "safety", can you really call it "safety"?Passing struct pointers is not always the norm. Passing class references *is* the norm (and actually the only way), even for scope classes, so there is much more chance for escape.You have to go out of your way to pass a struct by reference. You *can't* possibly pass a class by value, so they are more dangerous. In order to make scope safer, it has to be a type modifier in addition to a storage class, so the compiler can make reasonable decisions (like disallowing implicit casting to a non-scope version). <OT>Scope is really a dangerous hack to allocate a *reference type* on the stack. It's dangerous and kludgey, but in a performance-oriented language it's a necessary evil.You could say the same about structs.Why do all objects have monitor pointers anyway? The idea, that every object can act as lock, is a really bad one. But this is off-topic...All objects have a *placeholder* for a lock, which isn't allocated until the object is locked for the first time. It's a very pervasive idea in isn't "builtin" for D. IMO, mutexes without conditions is severely conditions. </OT> -Steve
Oct 23 2009
Steven Schveighoffer wrote:On Thu, 22 Oct 2009 12:50:21 -0400, grauzone <none example.net> wrote:I mentioned that "scope class T" thing in my original post too.dsimcha wrote:Your original question was about the statement "scope x = new X()", which can be done on any type of class, even non-scope ones.== Quote from grauzone (none example.net)'s article(I'm talking about scope classes as declared in "scope class T { ... }")Andrei Alexandrescu wrote: I'd really like to know why "scope x = new X();" is "unsafe", while encouraging doing exactly the same with structs seems to be a perfectly fine idea. Allocating structs on the stack is obviously not any safer than with classes. I don't remember the exact reasons why you wanted to turn "scope" into a library feature, but I think I remember something about discouraging it for safety reasons; please forgive me is this is wrong.Because classes in D are always passed by pointer. (Technically references, but really they're just pointers under the hood.) Returning a scope (stack-allocated) class from a function is equivalent to escaping a pointer to a stack variable. Returning a struct is done by value, just like returning an int.You can end up passing hidden pointers quickly, because a struct's this is a reference, and there are ref parameters and even ref returns. But Andrei is convinced that he can make passing refs safe, and as soon as they're safe, you're probably right.But you can't return scope classes from a function. You can't pass them as ref parameters either. They're designed to be safe. On the other hand, you can pass struct pointers all the way you want around, and it's damn unsafe. I don't get this "structs are safe because they are value types" argument anyway, because the this pointer for structs is a pointer/reference anyway. If it's trivial to break that "safety", can you really call it "safety"?Passing struct pointers is not always the norm. Passing class references *is* the norm (and actually the only way), even for scope classes, so there is much more chance for escape.That would have been possible. That type modifiers is already there and is partially implemented. But Andrei seems to have decided to go another way (using structs instead of classes), and this will become a useless feature. So I hope it will be removed to make the language less bloater.You have to go out of your way to pass a struct by reference. You *can't* possibly pass a class by value, so they are more dangerous. In order to make scope safer, it has to be a type modifier in addition to a storage class, so the compiler can make reasonable decisions (like disallowing implicit casting to a non-scope version).Scope is really a dangerous hack to allocate a *reference type* on the stack. It's dangerous and kludgey, but in a performance-oriented language it's a necessary evil.You could say the same about structs.<OT>I found it never useful. Even more, implicitly using an object as a lock makes the locking scheme more unclear ("which object is a lock and which isn't? I can't tell anymore!"), and I see lots of code that does the "Object lock = new Object();" thing to get rid of this confusion. Being able to use an object as lock looks like a nice, naive idea from the early days of multithreading, but it's obsolete now. Even if the allocation of the mutex is done lazily on demand, why add overhead to *all* light weight objects for such a useless thing?Why do all objects have monitor pointers anyway? The idea, that every object can act as lock, is a really bad one. But this is off-topic...All objects have a *placeholder* for a lock, which isn't allocated until the object is locked for the first time. It's a very pervasive idea inwhich isn't "builtin" for D. IMO, mutexes without conditions is severely limiting. The library condition variables aren't as nice asI always wondered how D could claim to make "big steps" into multithreading, without having any mechanism to signal other threads; it only had locks. The library condition variables from D runtime just fixed this hole from "outside".</OT> -Steve
Oct 23 2009