DIP44: scope(class) and scope(struct)

H. S. Teoh hsteoh at quickfur.ath.cx
Tue Aug 27 13:41:47 PDT 2013


On Wed, Aug 28, 2013 at 12:24:43AM +0400, Dmitry Olshansky wrote:
> 27-Aug-2013 18:25, H. S. Teoh пишет:
> >On Tue, Aug 27, 2013 at 04:01:57PM +0400, Dmitry Olshansky wrote:
> 
> >
> >What if move(r2) throws? Then res1 won't get cleaned up, because r1 has
> >already been nulled by the move.
> >
> 
> Then something is terribly wrong :)
> 
> Rule #1 of move should be is that it doesn't throw.

std.algorithm.move is not nothrow. I tried to make it nothrow, and the
compiler told me:

std/algorithm.d(1604): Error: 'object.TypeInfo.destroy' is not nothrow
std/algorithm.d(1596): Error: function 'std.algorithm.move!(DirIteratorImpl).move' is nothrow yet may throw
std/typecons.d(3522): Error: template instance std.algorithm.move!(DirIteratorImpl) error instantiating
std/file.d(2526):        instantiated from here: RefCounted!(DirIteratorImpl, cast(RefCountedAutoInitialize)0)
std/algorithm.d(1604): Error: 'object.TypeInfo.destroy' is not nothrow
std/algorithm.d(1596): Error: function 'std.algorithm.move!(Impl).move' is nothrow yet may throw
std/typecons.d(3522): Error: template instance std.algorithm.move!(Impl) error instantiating
std/net/curl.d(2040):        instantiated from here: RefCounted!(Impl)
std/algorithm.d(1604): Error: 'object.TypeInfo.destroy' is not nothrow
std/algorithm.d(1596): Error: function 'std.algorithm.move!(Impl).move' is nothrow yet may throw
std/typecons.d(3522): Error: template instance std.algorithm.move!(Impl) error instantiating
std/net/curl.d(2747):        instantiated from here: RefCounted!(Impl)
std/algorithm.d(1604): Error: 'object.TypeInfo.destroy' is not nothrow
std/algorithm.d(1596): Error: function 'std.algorithm.move!(Impl).move' is nothrow yet may throw
std/typecons.d(3522): Error: template instance std.algorithm.move!(Impl) error instantiating
std/net/curl.d(3065):        instantiated from here: RefCounted!(Impl)

Isn't that scary? This means that the case I presented above is not
hypothetical -- if destroy throws, then you're screwed.

(And yes it's a very very rare case... but would you want to find out
the hard way when a problem that triggers a throw in destroy slips past
QA testing 'cos it's so rare, and shows up in a customer's environment
where it may take hours or days or even months to debug?)

Basically, the only way to be 100% sure is to use scope(this), or the
manual equivalent thereof (see below).


> It just blits the damn data. Even if this is currently broken..

It also calls destroy, which is not nothrow. :)


> At the very least 2-arg version certainly can avoid throw even if T
> has a bogus destructor that goes bottom up on T.init.
> 
> BTW same with swap and at least in C++ that's the only way to avoid
> exceptions creeping up from nowhere.
[...]

Well this is kinda getting away from the main point, which is that
scope(this) allows us to hide all these dirty implementation details
under a compiler-implemented solution that makes sure things are always
done right. This is part of the reason I wrote DIP44: although it *is*
possible to manually write code that behaves correctly, it's pretty
tricky, and I doubt many people even realize the potential pitfalls.
Ideally, straightforward D code should also be correct by default.

Anyway, none of this move/nothrow nonsense is needed if we write things
this way:

	struct S {
		void delegate()[] __cleanups;
		Resource res1;
		Resource res2;

		this() {
			scope(failure) __cleanup();
			res1 = acquireResource();
			__cleanups ~= { res1.release(); };

			res2 = acquireResource();
			__cleanups ~= { res2.release(); };
		}

		~this() { __cleanup(); }

		private void __cleanup() {
			foreach_reverse (f; __cleanups)
				f();
		}
	}

Which is basically what DIP44 is proposing under a nicer syntax.


T

-- 
"640K ought to be enough" -- Bill G., 1984. "The Internet is not a primary goal for PC usage" -- Bill G., 1995. "Linux has no impact on Microsoft's strategy" -- Bill G., 1999.


More information about the Digitalmars-d mailing list