Skip to content
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

Add "Safe" (nullable) cast operator as? #399

Open
wrozwad opened this issue Jun 12, 2019 · 20 comments
Open

Add "Safe" (nullable) cast operator as? #399

wrozwad opened this issue Jun 12, 2019 · 20 comments
Labels
small-feature A small feature which is relatively cheap to implement.

Comments

@wrozwad
Copy link

wrozwad commented Jun 12, 2019

It'll be a little enhancement but so much pleasant one :)

Kotlin and Swift has something like Safe casting with as? operator. It gives an opportunity to casting with null fallback if cast can't be performed:

void someFunction(dynamic arg) {
  // we could has something like
  final classVariable = (arg as? SomeClass)?.someValue;
  
  // in place of
  final classVariable = arg is SomeClass ? arg.someValue : null;
}
@johnpryan
Copy link
Contributor

Thanks for filing an issue - just a head up, this issue tracker is for issues with the website. Language feature requests can be filed at https://github.com/dart-lang/language

@wrozwad
Copy link
Author

wrozwad commented Jun 12, 2019

Ahh, you're right, sorry ;)

@wrozwad wrozwad closed this as completed Jun 12, 2019
@kevmoo kevmoo transferred this issue from dart-lang/site-www Jun 12, 2019
@kevmoo
Copy link
Member

kevmoo commented Jun 12, 2019

Moved to the language repo!

@lrhn
Copy link
Member

lrhn commented Jul 10, 2019

I like this. I was honestly just about to file a request for the exact same thing (thanks to GitHub for listing relevant existing issues!)

Another practical use case would be;

int x = o as? int ?? 0;  // Could perhaps use some parentheses for readability :)

This is the safe cast operator that was originally requested when the current as operator was added, and it is very useful in a number of situations. See C# for prior art.

@kevmoo
Copy link
Member

kevmoo commented Jul 10, 2019 via email

@gnawf
Copy link

gnawf commented Aug 28, 2020

Definitely something I miss from Kotlin. Would be great timing to have this implemented this alongside the upcoming null safety.

@wjling
Copy link

wjling commented May 7, 2021

Any progress about this issue? I'm from Swift and suffered exceptions when casting from time to time.

@eernstg eernstg added the small-feature A small feature which is relatively cheap to implement. label May 7, 2021
@eernstg
Copy link
Member

eernstg commented May 7, 2021

We could also consider this to be a proposal to have a few extensions in a conveniently accessible library:

extension AsExtension on Object? {
  X as<X>() => this as X;
  X? asOrNull<X>() {
    var self = this;
    return self is X ? self : null;
  }
}

extension AsSubtypeExtension<X> on X {
  Y asSubtype<Y extends X>() => this as Y;
}

extension AsNotNullExtension<X> on X? {
  X asNotNull() => this as X;
}

void main() {
  num? n = 1 as dynamic;
  n.as<int>().isEven;
  n.asSubtype<int>().isEven; // `n.asSubtype<String>()` is an error.
  n.asNotNull().floor();
  n.asOrNull<int>()?.isEven; // Corresponds to `(n as? int)?.isEven`.
}

@dart-lang dart-lang deleted a comment from PhiFry Sep 20, 2021
@om-ha
Copy link

om-ha commented Oct 2, 2021

hey @eernstg

EDIT: This question is answered here: #399 (comment)

One question for the awesome extension you posted, why is it not possible to get it to work with dynamic objects?

For example:

import 'package:my_flutter_project/src/app/utils/type_utils.dart';

class SomeClass {
  /// This does NOT work.
  /// `as` or any other method within the extensions imported are not recognized!
  /// The import statement is marked as unused as well.
  void testCastingDynamicObject() {
    const dynamic someNumber = 1;
    final someInt = someNumber.as<int>();
    print(someInt);
  }

  /// This works!
  void testCastingNormalObject() {
    // const num someNumber = 5;
    // final someInt = someNumber.as<int>();
    // print(someInt);
  }

  /// This works also!
  void testCastingDynamicObjectWithPrecast() {
    // const dynamic someNumber = 1;
    // const Object someNumberObject = someNumber as Object;
    // final someInt = someNumberObject.as<int>();
    // print(someInt);
  }
}

@Levi-Lesches
Copy link

@om-ha, I believe it's because the extensions are declared on Object?. All types, like num and Object in your other two functions are subtypes of Object?. But since dynamic can be of any type, Dart won't try to check for extensions that could apply to it, including Object?, even though technically all objects are subtypes of Object?.

@om-ha
Copy link

om-ha commented Oct 3, 2021

Very insightful, thank you @Levi-Lesches.

I presumed dynamic objects should make dart check for Object? extensions since all objects basically are subtypes of it. Thanks for clearing that up.

@eernstg
Copy link
Member

eernstg commented Oct 4, 2021

I think I'll add a little bit more detail. There are actually some situations where the static type of an expression could be any type whatsoever, and we're still going to invoke an extension method on Object? on it. For example, in the body of f below, the value of X could be any type:

extension E on Object? {
  void foo() => print('Running E.foo!');
}

void f<X>(X x) {
  x.foo();
}

main() => f<num>(2); // 'Running E.foo!'.

So the fact that we don't run extension methods on receivers of type dynamic is a separate rule about that type.

dynamic receives special treatment (and so does Never) with extension methods, because they are considered to have all members, with all signatures. So if e as static type dynamic then for any m, if you try to do e.m(true, 'Hello') or e.m = 42, etc., the analyzer and compiler will trust the value of e to actually have a suitable member named m. You might get a dynamic error because the actual receiver doesn't have any such m at run time, but at compile time we assume that it does.

An extension method is only applicable in the case where the static type of the receiver does not have a member with that name (technically: with that 'basename', which is needed in order to include setters), and this means that we will simply never call an extension method on a receiver of static type dynamic or Never.

@om-ha
Copy link

om-ha commented Oct 4, 2021

Thank you @eernstg for the awesome explanation!

@gruvw
Copy link

gruvw commented Jan 2, 2022

Other use case:

[ for (final key in keys) box.get(key) as? Item ]

@gruvw
Copy link

gruvw commented Jan 3, 2022

No but let's say I wanted null if key not in box (to later take action on null elements from the list)

@Wdestroier
Copy link

Wdestroier commented Apr 25, 2022

// The list type is List<Item?>
[ for (final key in keys) box.get(key) as? Item ]

@gruvw The code above may add null to the list. Why not check it like below and keep the code null-safe?

// The list type is List<Item>
[ for (final key in keys) if (box.get(key) is Item) key ]

EDIT: Forgot to write the variable name after the loop

You may write less characters now, but in the future you will need to check if the element is not null for each list element.

for (final element in list) {
  if (element != null) { ... }
}

In the end you will lose null-safely, write more code and have a worse performance.

@lrhn
Copy link
Member

lrhn commented Apr 26, 2022

If we had null aware elements (#323), then

 [for (var key in keys) ?(box.get(key) as? Item)]

would work well.
But then,

 keys.map(box.get).whereType<Item>().toList()

already works.

The alternative needs an object after the if, so it would be:

[ for (final key in keys) if (box.get(key) is Item) box.get(key) as Item]

or it needs away to introduce a local variable. Say we had let/in:

[for (final key in keys) let value = box.get(key) in if (value is Item) value]

Still, nothing beats the brevity of the first version.

Edit: We now allow patterns in collection if:

[ for (final key in keys) if (box.get(key) case Item item) item) ]

Still not as short as the first version, but also more generally applicable.

We can implement as? as:

extension AsQ<T> on T {
  R? tryAs<R>() {
    var self = this; 
    return self is R ? self : null;
  }

That's longer than => this is R ? this : null, because we still don't have this promotion.

So it's not because the functionality cannot exist, just that o.tryAs<R>() doesn't look as nice as o as? R.
Should be just as efficient, if the tryAs function gets properly inlined.
(If we had generic getters, o.tryAs<R> would look slightly better than the o.tryAs<R>(). The () is just noise, but needed because generics only apply to functions and constructors.)

@dart-lang dart-lang deleted a comment from iulian0512 Oct 20, 2022
@seanhamstra
Copy link

Any chance this will be added to Dart? Super handy. atm we just use something like
value is SomeClass ? value as SomeClass : null

@Wdestroier
Copy link

@seanhamstra May I ask what is the complete function/method body?

@lrhn
Copy link
Member

lrhn commented Dec 13, 2022

With patterns, you should be able to do

  switch (value) {case SomeClass x => x, _ => null}

instead. Not much of a saving in size, but you don't need to repeat the value expression.

With #2664 it would be (value case SomeClass x) ? x : null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
small-feature A small feature which is relatively cheap to implement.
Projects
None yet
Development

No branches or pull requests