Open lrhn opened 1 year ago
Interesting! Note that this feature is somewhat similar to the proposals about adding other expressions with control flow, https://github.com/dart-lang/language/issues/2025, so it might be good to think about them as group.
I would like finally
clause, as well.
await mutex.lock();
var ret = try<E>(retrieveData()) finally { mutex.unlock(); };
In comparison with the example below, the type of ret
can be inferred.
And, mutex.unlock()
is proved even in case of exception other than E
, as well as the example below.
await mutex.lock();
RetType? ret;
try {
ret = retrieveData();
} on E {
ret = null;
} finally {
mutex.unlock();
}
I like the finally
clause. The only problem I see is that the suggested syntax won't allow you a try (e) finally { stmt }
which doesn't catch exceptions. You'd have to write that as try<Never>(e) finally {stmt}
, which is a little backwards.
Maybe make Never
the default catch-type if a finally
is supplied, and Object
if no finally
is supplied. The difference is still a little jarring.
I am not a syntax man, so I don't stick to such syntax.
a
try (e) finally { stmt }
which doesn't catch exceptions.
I'm not sure what does it mean.
await mutex.lock();
var ret = try /* <E> */ (retrieveData()) finally { mutex.unlock(); };
can't be treated as
await mutex.lock();
RetType? ret;
try {
ret = retrieveData();
} catch (e) {
ret = null;
} finally {
mutex.unlock();
}
?
The point is that if you only want the finally
functionality, and don't want to catch any errors, corresponding to:
await mutex.lock();
RetType ret;
try {
ret = retrieveData();
} finally {
mutex.unlock();
}
then you couldn't just do var ret = try (retrieveData()) finally { mutex.unlock(); }
, because the definition of try (expr)
given above would catch Object
. There was no syntax for try(...)
without catching, because it wasn't needed.
With finally
, it might actually be needed.
So we could change try (expr)
to not catch anything, but that's silly because then it doesn't do anything.
That's why I suggested:
try (e) // Catches any thrown object and converts it to `null`
try<E>(e) // Catches `E`s and converts them to `null`
try (e) finally {s} // Catches no objects, executes {s}` afterwards. (Does not promote to nullable).
try<E>(e) finally {S} // catches `E`s and converts them to `null`, executes `{s}` afterwards.
That gives the shortest syntax to the simplest usable behavior, but allows you to explicitly write a catch type to override it.
You can still write try<Never>(e)
to do ... well, nothing. Promote to nullable, I guess.
Or try<Object>(e) finally {s}
to catch every thrown object and do a finally afterwards.
and don't want to catch any errors
I did wanted catch all errors in my previous example.
I want some syntax not to catch any errors, as well.
But, I think this is another feature request so called "make try
able to be evaluated as expression" without introducing default null value, something like #27 and #307
The idea of an expression try/catch came up in #1884, as a shorthand for declaring variables whose initialization processes can fail. Instead of needing to write a whole try block with the declaration outside it so the variable stays in the scope you need it in, you could just declare it and its fallback all in one line: var v = try(getValue()) ?? fallback;
instead of
FooType v;
try {
v = getValue();
}
on Object{
v = fallback;
}
Having it evaluate to null rather than falling into a subsequent catch
expression means you can't extract info from the exception and do anything useful with it (e.g. log a warning). But for cases where you don't need that, it does seem elegant to be able to use the existing null-aware tools to specify the fallback, so I like this idea.
Though if finally
is being considered, I'd still like to suggest an optional catch
clause to the expression that can use the exception in the fallback. E.g. in try<E>(foo()) catch((e, st) => bar(e))
(not sure about that syntax; ideally the StackTrace could optionally be left off), if foo()
throws, use bar(e)
as the value instead, where e
is the exception (implicitly type E
). If foo
has static type T
, bar (and the overall try/catch expression) should have type T?
.
Sometimes you want to catch an exception and just return
null
to signal that something went wrong. That's currently cumbersome since catching exceptions can only happen as a statement.You can write a function like:
It suffers from needing two type arguments, where you only want to provide the
E
one, and you need to introduce a closure when using it, so:I propose introducing an expression form of
try
looking like:The
try
operator evaluatesexpression
to a value. Ifexpression
throws (if a type argument is provided, then only if it throws anE
), thetry
expression evaluates tonull
, otherwise it evaluates to the result of evaluatingexpression
.Proposal
Grammar:
It's a compile-time error if there is more than one type argument.
An expression of the form
try(e)
is equivalent to an expression of the formtry<Object>(e)
, whereObject
refers to the type fromdart:core
(whether it's in scope or not). You're allowed to writeObject?
ordynamic
as the type argument, it won't make any difference, the type is only used for subtype checks on values that are non-null
.We won't need to restrict an expression statement from starting with
try
, since the grammar fortry
expressions andtry
statements differ at the second token. The first token after an expressiontry
is(
or<, and it's
{` for the statement, so there is no parsing ambiguity.Semantics
Let e be an expression of the form
try<T>(e2)
or `try(e2)~.Static
Static typing of e with context type schema C proceeds as:
<Object>
, and letT
beObject
in the following.e
with context type schema NON_NULL(C) to a typeT2
. (We do not enforce the context type, or insert downcasts or implicit coercions here, any actual value will be passed through unchanged.)T2?
.Runtime
Evaluation of e proceeds as follows:
e2
.e2
completes with a 9c/value v, then evaluation of e completes with the value v.e2
throws a error o and stack trace s,T
, evaluation of e completes with the valuenull
.