On 10-08-30 11:29 AM, Sebastian Sylvan wrote:

I realise written dialogue sometimes comes across differently to the reader
than was intended by the writer. I shall try harder.

Thanks. A small bit of tone-effort applied early and often does wonders for preventing nightmarish threads of anger and misunderstanding, in my experience. A bit like brushing teeth :)

In practice this
leads to situations where variables get named things like "dummy" (or even
writing C preprocessor macros to auto-generate a name using __COUNTER__).
That seems to be beyond the spirit of RAII, and hurts readability IMO.

Oh? The uses I've seen in C++ are usually somewhat descriptive, say:

{
    Mutex guard(some_lock);
    do_stuff();
}

Reads ok to me. I don't imagine it being made much shorter or more succinct via lambdas. in Rust it'd look like so (assuming locking some external resource, since of course, inter-thread locking isn't quite a standard consideration in Rust code :) :

{
    auto guard = mutex(some_lock);
    do_stuff();
}

A bit longer than C++, but then, also no potential parse ambiguity. And I think still clear. What's the putative lambda-like form?

  with_mutex(some_lock,
             fn() {
                 do_stuff();
             })

Now we're just getting into aesthetics, where I usually err on the side of vertical / statement code orientation over "more nested expressions". But either way, we're not talking orders of magnitude, just shuffling a similar cost around. That alone wouldn't motivate the feature, I think.

Permitting tidy solutions to parallel iteration, or other existing control features .. or simply being much faster than a heap-allocated binding for capturing a local; *those* are compelling arguments :)

You may want to consider a "scope" feature like D has, which allows you to
put code at the end of the current scope (regardless of how it leaves it).
My "transaction" example would work just as well, better actually,  and
would certainly be a lot cleaner than with the try/finally stuff in there.

It's not the association-with-a-scope-exit aspect that's hard to get right. It's the initialization-status-tracking. Here's my point. Suppose I write this:

thing x;
try {
   a();
   x = acquire();
   b();
} finally {
   // This is likely a bug.
   release(x);
}

That's likely incorrect code; I don't know if I can safely release x in the finally block, because I don't know it was acquired. The a() call might have failed. The code's only correct with some kind of resource that never gets upset with unbalanced release calls. That's not a universal quality of managed resources. To be correct in general, I need to write this instead:


thing x;
bool x_was_acquired = false;
try {
   a();
   x = acquire();
   x_was_acquired = true;
   b();
} finally {
   if (x_was_acquired) { release(x); }
}

and I have to do this recursively (with relatively awkward nesting try/finally blocks and additional staged flags indicating initialized-ness) any time the acquisition itself is non-atomic and/or x has substructure. It can be a challenge to emulate RAII convincingly with try/finally. It's possible, but also easier to get wrong and ship code that has buggy cleanup paths.

The RAII style composes better and always -- only -- releases the stuff that was successfully acquired. The language has rules to track initialization status and those rules are recycled for tracking "when to call which destructor". RAII-on-scopes is not some misfeature that should be replaced with try/finally; C++ knew about try/finally and picked RAII because it was considered better. It's one of the more useful innovations of C++. See the designer's note:

  http://www2.research.att.com/~bs/bs_faq2.html#finally

-Graydon
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to