Could the RAII-styled second option be rewritten as follows?

   do |cond, handler| {
       cond.add(handler);
       do_some_stuff();
       that_might_raise();
       out_of_kittens();
   }(OutOfKittens) |t| {
       UseAardvarksInstead
   }

This has the advantage of requiring neither block to be surrounded by
parentheses, nor requiring any nebulous unused temporary variable. It also
puts the error-handling code after the guarded code, if you're a fan of
try/catch.

(If this is just mind-bendingly bizarre or outright wrong, please excuse me
as I've been up all night.)

On Fri, Oct 19, 2012 at 7:48 PM, Graydon Hoare <gray...@mozilla.com> wrote:

> Hi,
>
> If you're going to respond to this email, please read it in full, don't
> just skim.
>
> I've been sketching a module in core::condition this week that
> implements a condition-handling system on top of the TLS feature
> (task::local_data) that Ben Blum added over the summer.
>
> This email is a very specific description of what I'm adding (many
> people have asked) along with a very specific poll about how best to
> structure the outermost part of the API.
>
> I'd appreciate replies here be kept focused on those points --
> clarifying questions + indication of preference on API -- as there are a
> lot of new people on the list since we last discussed this, but we are
> _not_ changing plan here. This is the same plan we've had for 6-ish
> years, off-and-on, sketched numerous times, whenever it seems
> implementable with the pieces lying around. I'm not interested in having
> a big discussion of all the possible ways condition-systems can be
> built, or (re-)exploring the design-space.
>
> Some references to the lurking plan here:
>
> https://mail.mozilla.org/pipermail/rust-dev/2012-May/001879.html
> https://mail.mozilla.org/pipermail/rust-dev/2011-November/000999.html
> https://github.com/mozilla/rust/issues/1945#issuecomment-4425610
>
> Anyway, here is the system (again) in a nutshell:
>
>   - Tasks have a task-local storage system (TLS). TLS maps keys to
>     values that stick to the task until released. The keys are
>     addresses, conventionally static addresses of items (this is a
>     hack for producing 'unique' keys, but it works).
>
>   - Handlers are stack-closures that get pushed and popped from TLS.
>     They obey stack discipline and can capture their lexical environment
>     since they'll be popped before the pushing frame exits.
>
>   - Handlers are put in TLS keyed to a condition. A condition is a
>     static, symbolic value used mostly for its TLS-keying and
>     method-dispatching capabilities. Conditions don't store values,
>     they just name situations that might occur and give-type-to any
>     values passed-to and returned-from potential handlers of such
>     situations.
>
>   - Code can raise a condition whenever it likes. No static checking
>     of which conditions might occur where; no relationship to function
>     types. Raising of a condition in a module is indicated by the
>     condition being present in the module, by convention, or by
>     documentation.
>
>   - Raising a condition can pass a value giving further details on
>     the condition, beyond just its type/name. If the raise is handled,
>     a value may return. The types of values raised-to and returned-from
>     handlers is specified by a condition. That is, a condition is
>     declared like "const OutOfKittens : Condition<T,U> = ..." where
>     T is the type of values passed to an OutOfKittens handler, and
>     U is the type of values returned from an OutOfKittens handler when
>     it is returning a solution to the condition. U might well be a
>     symbolic type such as an enum of reasonable recovery strategies.
>
>   - There is no change to the fail/unwind system. Failure is still
>     unrecoverable, results in unwinding, and is the only possible
>     cause of unwinding. If a condition is handled, it doesn't unwind.
>     If it's unhandled, or if the handler fails, the task fails and
>     unwinds. This is not an unwind-rewind system or an unwind-catch
>     system.
>
>   - Condition.raise is a normal function and does something very simple:
>     - look in TLS to see if there's a handler
>     - if so, call it and return the result to the raiser
>     - if not, fail
>
>   - This means condition-handling happens _at site_ of raising. If
>     the handler returns a useful value, processing continues as if
>     nothing went wrong. It's _just_ a rendezvous mechanism for an
>     outer frame to dynamically provide a handler closure to an inner
>     frame where a condition occurs.
>
> Given that description, astute readers may note that there's not much to
> this "system", mostly just convention and existing pieces. This is
> intentional. It's supposed to be very minimal and only took about a day
> to get working, beyond the miserable slog Ben already fought through
> implementing TLS over the summer.
>
> Nonetheless I want to get it _right_ since this is probably going to be
> a convention that sticks and gets used extensively across our libraries.
> Getting it nailed down is a priority for the 0.5 cycle, as we're trying
> to knock out the top few remaining toe-stubs when writing libraries, and
> this is one of them. We've grown an unfortunate dependence on using the
> core::Result type for managing recoverable-problems in library code and
> I would like to move away from that in as many cases as practical, since
> at-site handling tends to be much cleaner for users. Since 'Result' is
> so clunky we wind up using unrecoverable 'fail' in a lot of cases too,
> sometimes cases where having the option to recover makes sense.
>
> So: API survey. Modulo a few residual bugs on consts and function
> regions (currently hacked around in the implementation), I have 3
> different APIs that all seem to work -- which I've given different names
> for discussion sake -- and I'm trying to decide between them. They
> mostly differ in the number of pieces and the location and shape of the
> boilerplate a user of the condition system has to write. My current
> preference is #3 but I'd like a show of hands on others' preferences.
>
> Here they are:
>
> 1. "Protect/handle", a.k.a. "make it look like try/catch": this version
> pursues the arrangement with protected-code coming first and handlers
> coming second. To do this, the protected-code has to be suspended in a
> closure, which is then passed to a temporary struct, which is
> responsible for both capturing the remaining handler-part and firing the
> push/run/pop cycle together at the end. Uses of it look like this:
>
>   let b = do OutOfKittens.protect {
>       do_some_stuff();
>       that_might_raise();
>       out_of_kittens();
>   };
>
>   do b.handle |t| {
>       UseAardvarksInstead
>   }
>
> It's not really an ideal use of our 'do' syntax, nor are many other
> variants of this API I've tried. It might read slightly better (or
> worse) as such:
>
>   (do OutOfKittens.protect {
>       do_some_stuff();
>       that_might_raise();
>       out_of_kittens();
>   }).handle(|t|
>       UseAardvarksInstead
>   );
>
>
> 2. "Guard", a.k.a. "make it look like RAII": this version has only a
> single "floating in space" handler-block that is assigned to a temporary
> guard object in the current scope, and all subsequent code in that scope
> is protected. Reads like this:
>
>   {
>       let _g = OutOfKittens.guard(|t| UseAardvarksInstead);
>       do_some_stuff();
>       that_might_raise();
>       out_of_kittens();
>   }
>
> Downsides of this mechanism are that a user might try to move _g out of
> the current frame or otherwise tamper with it -- we might need to do
> some tricks with the region system to prevent that -- and it gives rise
> to this somewhat artificial _g variable and otherwise unclear extra
> block to contain it.
>
> 3. "Trap/in", a.k.a. "the system I like the look of best": this is the
> version Patrick suggested yesterday. It sets up the handlers in the head
> of a do-block and then invokes the protected code. It's order-inverted
> from protect/handle -- the handlers come before the protected code --
> but it still reads ok to my eyes, and seems to play nicely with our
> existing syntax:
>
>   do OutOfKittens.trap(|t| UseAardvarksInstead).in {
>       do_some_stuff();
>       that_might_raise();
>       out_of_kittens();
>   }
>
> Opinions? Clarifying questions?
>
> Thanks,
>
> -Graydon
> _______________________________________________
> Rust-dev mailing list
> Rust-dev@mozilla.org
> https://mail.mozilla.org/listinfo/rust-dev
>
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to