www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - this() not executing code on structs

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
next sibling parent reply zoli <zoli freemail.hu> writes:
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
parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from zoli (zoli freemail.hu)'s article
 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.

(allocate on heap, allocate on stack, etc.?), there is no main drawback of using

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.
 I don't think if there's any reason to use struct with member functions from

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
parent reply zoli <galap freemail.hu> writes:
 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

Yes there is. How about the cost of storing vtbl and monitor? Actually, just

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().
Oct 21 2009
parent dsimcha <dsimcha yahoo.com> writes:
== Quote from zoli (galap freemail.hu)'s article
 If the new "operator" for the class is redesigned to have multiply



 (allocate on heap, allocate on stack, etc.?), there is no main drawback of
using

Yes there is. How about the cost of storing vtbl and monitor? Actually, just

And what about the final classes (with no ancestors)?

Even final classes w/ no explicit ancestors are implicitly subclasses of Object, which defines virtual functions. Therefore, *all* class instances must have a vtbl pointer.
Oct 21 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
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.


 Andrei

I 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
prev sibling next sibling parent reply Bartosz Milewski <bartosz-nospam relisoft.com> writes:
Andrei Alexandrescu Wrote:

     this() { myCount = count++; }       // ERROR

It'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
next sibling parent reply Don <nospam nospam.com> writes:
Bartosz Milewski wrote:
 Andrei Alexandrescu Wrote:
 
     this() { myCount = count++; }       // ERROR

It'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.

Ouch. It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.
Oct 23 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Don wrote:
 Bartosz Milewski wrote:
 Andrei Alexandrescu Wrote:

     this() { myCount = count++; }       // ERROR

It'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.

Ouch. It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.

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. Andrei
Oct 23 2009
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 On Fri, 23 Oct 2009 18:46:47 +0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 
 Don wrote:
 Bartosz Milewski wrote:
 Andrei Alexandrescu Wrote:

     this() { myCount = count++; }       // ERROR

It'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.

It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.

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. Andrei

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.

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. Andrei
Oct 23 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Fri, 23 Oct 2009 18:46:47 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Don wrote:
 Bartosz Milewski wrote:
 Andrei Alexandrescu Wrote:

     this() { myCount = count++; }       // ERROR

It'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.

It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.

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. Andrei

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.
Oct 23 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Fri, 23 Oct 2009 19:40:33 +0400, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 On Fri, 23 Oct 2009 18:46:47 +0400, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 Don wrote:
 Bartosz Milewski wrote:
 Andrei Alexandrescu Wrote:

     this() { myCount = count++; }       // ERROR

It'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.

It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.

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. Andrei

(unless a variable is nullable). This will also remove a lot of bloat from executables.

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. Andrei

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)
Oct 23 2009
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
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:

 Denis Koroskin wrote:
 On Fri, 23 Oct 2009 18:46:47 +0400, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 Don wrote:
 Bartosz Milewski wrote:
 Andrei Alexandrescu Wrote:

     this() { myCount = count++; }       // ERROR

It'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.

It's because it's interpreting foo() as a struct literal. If a struct has any constructors, struct literals should be disabled.

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. Andrei

(unless a variable is nullable). This will also remove a lot of bloat from executables.

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. Andrei

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)

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?).
Oct 23 2009
prev sibling next sibling parent Bartosz Milewski <bartosz-nospam relisoft.com> writes:
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
prev sibling next sibling parent Leandro Lucarella <llucax gmail.com> writes:
Bartosz Milewski, el 21 de octubre a las 16:33 me escribiste:
 Andrei Alexandrescu Wrote:
 
     this() { myCount = count++; }       // ERROR

It'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.

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 Vaporeso
Oct 21 2009
prev sibling next sibling parent Rainer Deyke <rainerd eldwood.com> writes:
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
prev sibling parent reply grauzone <none example.net> writes:
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
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling next sibling parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from grauzone (none example.net)'s article
 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 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
parent reply grauzone <none example.net> writes:
dsimcha wrote:
 == Quote from grauzone (none example.net)'s article
 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'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"?
 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.

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...
 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
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
grauzone wrote:
 dsimcha wrote:
 == Quote from grauzone (none example.net)'s article
 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'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.

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.
 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
parent reply grauzone <none example.net> writes:
Andrei Alexandrescu wrote:
 grauzone wrote:
 dsimcha wrote:
 == Quote from grauzone (none example.net)'s article
 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'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.

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.

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).
 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.

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.)
 
 Andrei

Oct 22 2009
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
grauzone wrote:
 Andrei Alexandrescu wrote:
 grauzone wrote:
 dsimcha wrote:
 == Quote from grauzone (none example.net)'s article
 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'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.

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.

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).
 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.

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?

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. Andrei
Oct 22 2009
prev sibling parent grauzone <none example.net> writes:
Steven Schveighoffer wrote:
 On Thu, 22 Oct 2009 12:50:21 -0400, grauzone <none example.net> wrote:
 
 dsimcha wrote:
 == Quote from grauzone (none example.net)'s article
 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.

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'm talking about scope classes as declared in "scope class T { ... }")

Your original question was about the statement "scope x = new X()", which can be done on any type of class, even non-scope ones.

I mentioned that "scope class T" thing in my original post too.
 
 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 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.
 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.

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).

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.
 <OT>
 
 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 other successful languages like Java and C#. Having used it extensively in C#, I find it very easy to use and well designed.

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?
 However, in C#, there is support for conditions on objects as well, 
 which isn't "builtin" for D.  IMO, mutexes without conditions is 
 severely limiting.  The library condition variables aren't as nice as 
 C#'s builtin conditions.

I 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
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Thu, 22 Oct 2009 21:01:15 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 grauzone wrote:
 dsimcha wrote:
 == Quote from grauzone (none example.net)'s article
 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.

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.
 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

Scope classes could be disallowed in SafeD, but you can't disallow Scope!(Object).
Oct 22 2009
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 22 Oct 2009 12:50:21 -0400, grauzone <none example.net> wrote:

 dsimcha wrote:
 == Quote from grauzone (none example.net)'s article
 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.

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'm talking about scope classes as declared in "scope class T { ... }")

Your original question was about the statement "scope x = new X()", which can be done on any type of class, even non-scope ones.
 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.
 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.

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>
 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 other successful languages like Java and C#. Having used it extensively in C#, I find it very easy to use and well designed. However, in C#, there is support for conditions on objects as well, which isn't "builtin" for D. IMO, mutexes without conditions is severely limiting. The library condition variables aren't as nice as C#'s builtin conditions. </OT> -Steve
Oct 23 2009