Add "Safe" (nullable) cast operator `as?`
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;
}
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
Ahh, you're right, sorry ;)
Moved to the language repo!
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.
I like it, too! I missed this when I came from C#
Definitely something I miss from Kotlin. Would be great timing to have this implemented this alongside the upcoming null safety.
Any progress about this issue? I'm from Swift and suffered exceptions when casting from time to time.
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`.
}
hey @eernstg
EDIT: This question is answered here: https://github.com/dart-lang/language/issues/399#issuecomment-933255132
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);
}
}
@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?.
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.
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.
Thank you @eernstg for the awesome explanation!
Other use case:
[ for (final key in keys) box.get(key) as? Item ]
No but let's say I wanted null if key not in box (to later take action on null elements from the list)
// 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.
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.)
Any chance this will be added to Dart? Super handy. atm we just use something like
value is SomeClass ? value as SomeClass : null
@seanhamstra May I ask what is the complete function/method body?
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.