Jump to page: 1 2
Thread overview
DIP44: scope(class) and scope(struct)
Aug 24, 2013
H. S. Teoh
Aug 24, 2013
Andrej Mitrovic
Aug 24, 2013
Andrej Mitrovic
Aug 24, 2013
H. S. Teoh
Aug 24, 2013
Ramon
Aug 24, 2013
Simen Kjaeraas
Aug 24, 2013
Ramon
Aug 24, 2013
Meta
Aug 24, 2013
Piotr Szturmaj
Aug 24, 2013
Artur Skawina
Aug 24, 2013
deadalnix
Aug 24, 2013
Tobias Pankrath
Aug 24, 2013
H. S. Teoh
Aug 24, 2013
Dmitry Olshansky
Aug 24, 2013
Walter Bright
Aug 25, 2013
H. S. Teoh
Aug 25, 2013
Walter Bright
Aug 25, 2013
H. S. Teoh
Aug 25, 2013
Walter Bright
August 24, 2013
I've written up a proposal to solve the partially-constructed object problem[*] in D in a very nice way by extending scope guards:

	http://wiki.dlang.org/DIP44

[*] The partially-constructed object problem is when you have a class (or struct) that must acquire some number of external resources, usually to set them as member fields, but before the ctor is able to initialize all of these fields, an Exception is thrown. Now the object is in a partially-constructed state: some member fields have been initialized, and need to be destructed in order to release the associated external resources, but other fields are still uninitialized so should not be destructed. This leads to the problem of, how do we clean up in this situation? We can't call the dtor -- the dtor assumes *all* fields have been set and will wrongly try to release resources that haven't been acquired yet. But we can't ignore the issue either -- the resources that *have* been required need to be released somehow. This DIP proposes a nice solution to this problem that fits in very well with the existing scope guards in D.

Destroy! ;-)


T

-- 
Only boring people get bored. -- JM
August 24, 2013
On Saturday, 24 August 2013 at 00:45:46 UTC, H. S. Teoh wrote:
> Destroy! ;-)

I won't destroy but I'll say avoid the struct/class keywords and use `scope(this)` instead, it looks nicer and will work easier in generic code.
August 24, 2013
On Saturday, 24 August 2013 at 00:48:46 UTC, Andrej Mitrovic wrote:
> On Saturday, 24 August 2013 at 00:45:46 UTC, H. S. Teoh wrote:
>> Destroy! ;-)
>
> I won't destroy but I'll say avoid the struct/class keywords and use `scope(this)` instead, it looks nicer and will work easier in generic code.

Perhaps scope(~this) is actually more appropriate.
August 24, 2013
On Sat, Aug 24, 2013 at 03:06:39AM +0200, Andrej Mitrovic wrote:
> On Saturday, 24 August 2013 at 00:48:46 UTC, Andrej Mitrovic wrote:
> >On Saturday, 24 August 2013 at 00:45:46 UTC, H. S. Teoh wrote:
> >>Destroy! ;-)
> >
> >I won't destroy but I'll say avoid the struct/class keywords and use `scope(this)` instead, it looks nicer and will work easier in generic code.
> 
> Perhaps scope(~this) is actually more appropriate.

Hmm. You do have a point. But I'm not sure I like breaking the pattern of a single identifier inside scope(). I still think scope(this) looks nicer and is nicer to type as well. And also reads like "scope of this object", which kinda conveys the intent.

But anyway, we can bikeshed over the exact syntax later. What of the proposal itself? Does it make any sense? Any obvious glaring flaws?

Also, it just occurred to me that Adam's manual implementation can be made to handle partial object construction as well, by using scope(failure) in the ctor:

	struct S {
		void delegate()[] cleanupFuncs;
		Resource1 res1;
		// ...
		this() {
			// assuming the dtor isn't invoked if the ctor
			// throws.
			scope(failure) cleanup();

			res1 = acquireResource();
			cleanupFuncs ~= { res1.release(); }
			...
		}

		void cleanup() {
			foreach_reverse (f; cleanupFuncs)
				f();
		}

		~this() { cleanup(); }
	}

So scope(this) could simply be lowered to the above code, perhaps with
some compiler low-level handling for the potential problem with calling
a method from ~this(), which I think can be a problem in the current
implementation of dtors? (IIRC 'this' may be invalid when ~this() is
invoked, or something dangerous like that?)

In any case, the above code has a lot of boilerplate which scope(this) would eliminate, besides expanding the concept of scope guards to object lifetimes.


T

-- 
Unix was not designed to stop people from doing stupid things, because that would also stop them from doing clever things. -- Doug Gwyn
August 24, 2013
I don't want to be picky but I feel one shouldn't pack too much into one thing and in a way break the logic flow and possibly introduce limitations.

The basic rule is to clean up in the destructor what has been allocated/aquired in the constructor - and this shouldn't be interrupted but stay that way.
What you want to address, if I got it right (If not, I apologize for my interruption) is the problem of incoherent state, i.e. (relating to your example) to have res1 and res2 aquired but not res3.
In my (possibly strongly limited) understanding scope(failure) is the right approach.

Having a mechanism (as suggested) that also takes care of the dtor's job breaks the normal logic and might create new problems or limitations by implicitly putting a dtor job into a ctor mechanism.

What, for instance, if I aquire 10 resources in ctor and during normal programm execution cleanup 7 of them, so only some are left for dtor?


If scope(failure) does already work in ctor, just keep it that way. If not,HS Teoh's idea to make it work is great. But that about it. Don't add more functionality in it. Normal ctor/dtor working should be kept and so should scope(). Whatever user wants beyond that can be coded by him. Don't break major flow for some luxury (having scope in ctor isn't luxury, having it reach into dtor, too up to the point of making dtor superflous is).

scope is a brilliant feature. But I feel we shouldn't have it do "miracles", even less behind curtains.

In case what I said is just plain stupid and I should have shut up as newbie, I apologize and won't disturb any more.
August 24, 2013
"With this extension to scope guards, class and struct destructors will practically not be needed anymore, since scope(this) will take care of cleaning up everything."

I can't think of an example off the top of my head, but is it really okay to conflate destruction due to an error during construction, and destruction over the regular course of a struct's usage? What if one instance requires different code from the other? Maybe require that scope(this) statements *only* be run if there is an error during construction, while just the destructor will be run normally?
August 24, 2013
H. S. Teoh wrote:
> I've written up a proposal to solve the partially-constructed object
> problem[*] in D in a very nice way by extending scope guards:
>
> 	http://wiki.dlang.org/DIP44
>
> Destroy! ;-)

I see some possible implementation problem. Using a scope guard in a single function is straightforward because code is executed in the same scope.

However, in your proposal scope guard is split to two different functions/scopes. This may require copying local stack variables and storing them somewhere if they're referred to in the scope(this).

For example:

ResourceManager resManager;

class C {
    Resource res;
    this(int resourceId) {
        res = resManager.acquireResource(resourceId);
        scope(this) resManager.releaseResource(resourceId);
    }
}

Here the resourceId value must be stored somewhere so it can be used later, when the object is destructed.

I'm thinking of two possible "solutions" to this problem:

1. disallow referring to local variables in scope(this). In this case resourceId should be stored in a field of class C or in the Resource itself (it may not always be allowed).

2. allow referring to local variables and create the closure. This causes additional memory allocation and also reference to the closure must be stored somewhere, perhaps in the class hidden field. Of course, this is a "no go", I'm writing this here for comparison.

August 24, 2013
On Sat, 24 Aug 2013 03:34:32 +0200, Ramon <spam@thanks.no> wrote:

> What, for instance, if I aquire 10 resources in ctor and during normal programm execution cleanup 7 of them, so only some are left for dtor?

Then they don't have the same lifetime as the object, and scope(this)
would be the wrong tool for the job.

-- 
Simen
August 24, 2013
On 08/24/13 08:30, Piotr Szturmaj wrote:
> H. S. Teoh wrote:
>> I've written up a proposal to solve the partially-constructed object problem[*] in D in a very nice way by extending scope guards:
>>
>>     http://wiki.dlang.org/DIP44

> 2. allow referring to local variables and create the closure. This causes additional memory allocation and also reference to the closure must be stored somewhere, perhaps in the class hidden field. Of course, this is a "no go", I'm writing this here for comparison.

That is what he's actually proposing. And, yes, it's not a good idea. Implementing it via runtime delegates, implicit captures/allocs and extra hidden fields inside aggregates would work, but the cost is too high. It's also not much of an improvement over manually registering and calling the delegates. Defining what happens when a ctor fails would be a good idea, having a cleanup method which defaults to `~this`, but can be overridden could help too.

There are other problems with that DIP, like making it harder to see what's actually going on, by splitting the dtor code and having it interleaved with another separate flow.


It *is* possible to implement a similar solution without any RT cost,
but it would need:
a) flow analysis - to figure out the cleanup order, which might not be
    statically known (these cases have to be disallowed)
b) a different approach for specifying the cleanup code, so that
    implicit capturing of ctor state doesn't happen and it's not
    necessary to read the complete body of every ctor just to find
    out what a dtor does.


artur
August 24, 2013
On Saturday, 24 August 2013 at 00:45:46 UTC, H. S. Teoh wrote:
> I've written up a proposal to solve the partially-constructed object
> problem[*] in D in a very nice way by extending scope guards:
>
> 	http://wiki.dlang.org/DIP44
>
> [*] The partially-constructed object problem is when you have a class
> (or struct) that must acquire some number of external resources, usually
> to set them as member fields, but before the ctor is able to initialize
> all of these fields, an Exception is thrown. Now the object is in a
> partially-constructed state: some member fields have been initialized,
> and need to be destructed in order to release the associated external
> resources, but other fields are still uninitialized so should not be
> destructed. This leads to the problem of, how do we clean up in this
> situation? We can't call the dtor -- the dtor assumes *all* fields have
> been set and will wrongly try to release resources that haven't been
> acquired yet. But we can't ignore the issue either -- the resources that
> *have* been required need to be released somehow. This DIP proposes a
> nice solution to this problem that fits in very well with the existing
> scope guards in D.
>
> Destroy! ;-)
>
>
> T

I like the feature. I wouldn't say this is the most important thing to add here, the same can be achieved with scope(failure) and destructor.
« First   ‹ Prev
1 2