Skip to content

Add Kotlin-like someNullableExpression ?: return for defensive programming #2088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
fzyzcjy opened this issue Feb 2, 2022 · 8 comments
Closed
Labels
feature Proposed language feature that solves one or more problems

Comments

@fzyzcjy
Copy link

fzyzcjy commented Feb 2, 2022

When writing Flutter code, we frequently need to do defensive programming - since we never want the user to see crashes. For example:

String? f(String a) {
  var b = g(a);
  if (b==null) {
    Log.warn("f skip running since b==null");
    return null;
  }

  var c = h(a, b);
  if (c==null) {
    Log.warn("f skip running since c==null");
    return null;
  }

  ...

  return something;
}

With the proposed operator, we can simply write down:

String? f(String a) {
  var b = g(a) ?: return _withLogging("f skip running since b==null");
  var c = h(a, b) ?: return _withLogging("f skip running since c==null");
  ...

  return something;
}

which is much simpler to read.

p.s. the withLogging is a very simple helper, only define once throughout whole program:

T? _withLogging<T>(String message) { Log.warn(message); return null; }
@fzyzcjy fzyzcjy added the feature Proposed language feature that solves one or more problems label Feb 2, 2022
@Levi-Lesches
Copy link

Seems like you're essentially asking to allow return after ??. This is a fairly common request, and you can already write nullableExpression ?? throw "error", so allowing a return there would make the ?? more consistent.

String? f(String a) {
  var b = g(a) ?? return _log("f skip running since b==null");
  var c = h(a, b) ?? return _log("f skip running since c==null");

  return c;
}

For what it's worth, your code can already be made simpler:

String? f(String a) {
  var b = g(a) ?? _log("f skip running since b==null");
  if (b == null) return null;

  var c = h(a, b) ?? _log("f skip running since c==null");
  if (c == null) return null;

  return c;
}

@mateusfccp
Copy link
Contributor

Seems like #2025. Although I am not against adding this possibility, I hardly disagree that your example is clearer/easier to understand.

@fzyzcjy
Copy link
Author

fzyzcjy commented Feb 2, 2022

@Levi-Lesches Thanks!

you're essentially asking to allow return after ??

Seems yes!

For what it's worth, your code can already be made simpler:

I may consider that, though I am not very sure whether this shorter code is very readable

@mateusfccp Thanks!

Although I am not against adding this possibility, I hardly disagree that your example is clearer/easier to understand.

Hmm then what about this example:

bool f(String a) {
  var b = g(a);
  if (b==null) {
    return false;
  }

  var c = h(a, b);
  if (c==null) {
    return false;
  }

  ...

  return true;
}

vs

String? f(String a) {
  var b = g(a) ?: return false;
  var c = h(a, b) ?: return false;
  ...
  return true;
}

@mateusfccp
Copy link
Contributor

Yeah, this one is better.

@lrhn
Copy link
Member

lrhn commented Feb 2, 2022

Making return an expression (which is what is required to allow it after ??) is definitely possible, but does make control flow analysis harder. (Probably not much harder, since we already allow and properly analyze throws as expressions.)

Some internals in the compilers might need to change, because they can no longer assume that returns only happen in statements. I have no idea if it's true, but it could easily be true that the internal flow graph doesn't allow outgoing links from expressions. So, likely some necessary and complicated work there.

If we do allow expression returns, we should likely also allow break and continue as expressions. Maybe even rethrow, that would be the complete set of control transfer statements.
(Not yield/yield*, since they're only conditionally doing return, and they have no value if they don't. We could give yield a value, probably the one it yielded, and allow it as an expression too, but yield* doesn't have any natural value, and acts more like a loop than a single operation).

@Levi-Lesches
Copy link

It sounds like this is becoming an exact duplicate of #2025. @fzyzcjy, I'd suggest you go there and add a 👍 to that issue.

@fzyzcjy
Copy link
Author

fzyzcjy commented Feb 3, 2022

@irhn Thanks!

@Levi-Lesches Thanks I have done it

@Levi-Lesches
Copy link

Maybe close this issue as well so as to not fill up the issues page?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

4 participants