Dart 3.0: brief overview
Heads up folks! Only three days ago at Google I/O, the new Dart 3.0 was unveiled! Like a piñata bursting open, it's spilling with a bunch of fun features: records, pattern-matching, and class modifiers. So, grab your biscuits, pour a hot cuppa, and let's dive into this tech fiesta! 🚀
Records
Ever wished your function could return more than one value, like a generous granny handing out sweets? Well, Dart 3.0's got you covered!
/// function that returns a tuple of two strings
(String, String) getToken() => ('access', 'refresh');
void main(List<String> args) {
// create two variables and assign them to the values
// returned by the function
final (accessToken, refreshToken) = getToken();
}
Record with positional params
Voila! We just made a function return two values. But there's more! Dart 3.0's records have a softer side; they support named parameters. Let's spice up our codebase:
/// function that returns a tuple of two strings
({String access, String refresh}) getToken() => (access: 'access', refresh: 'refresh');
void main(List<String> args) {
// create two variables and assign them
// to the values returned by the function
var (:access, :refresh) = getToken();
print(access);
print(refresh);
}
Record with named params
Records quick tip
Neat, isn't it? But what if your record has more fields than a corn farm? Don't worry, Dart has got a neat trick up its sleeve:
// define typedef for a function
typedef TokenPair = ({String access, String refresh});
// use typedef as a return value
TokenPair getToken() => (access: 'access', refresh: 'refresh');
void main(List<String> args) {
// create two variables and assign them to the values returned by the function
var (:access, :refresh) = getToken();
print(access);
print(refresh);
}
Records with typedef
Destructuring
Speaking of magic tricks, Dart 3.0 also introduces destructuring. This nifty feature lets you unpack values from arrays or properties from objects into distinct variables, like a magician pulling rabbits out of a hat!
void main(List<String> args) {
final list = ['Hello1', 'Hello2'];
final [hello1, hello2] = list;
print(hello1); // prints 'Hello1'
print(hello2); // prints 'Hello2'
}
Applied destructuring to list
In this magic trick, I created two variables with list destructuring. But the fun doesn't stop there. Did you know you can use destructuring with Dart classes? Brace yourself!
void main(List<String> args) {
final person = Person('John', 30, 'New York');
final Person(:name, :age, :address) = person;
print(name); // Prints John
print(age); // Prints 30
print(address); // Prints New York
}
class Person {
final String name;
final int age;
final String address;
Person(this.name, this.age, this.address);
}
Applied destructuring to a class Person
Plus, with destructuring, swapping variable values is as easy as swapping socks:
void main(List<String> args) {
var a = 1;
var b = 2;
[a, b] = [b, a];
print(a); // Prints 2
print(b); // Prints 1
}
Now we're cooking with gas! But wait, there's more. Let's take a look at class modifiers.
Class Modifiers
Now we are moving to classes. Finally, they got modifiers! Let's list them:
interface class
- Can't be extended, only implemented. Like a stubborn mule, it refuses to change its ways.base class
: Can't be implemented, only extended. It's like a family heirloom, passing on its legacy.final class
: Can't be extended or implemented. Think of it as the lone wolf of classes.mixin class
: Can be mixed in. It's the social butterfly in the world of classes.sealed class
: Provides exhaustive check. The detective of classes, leaving no stone unturned.

// file_a.dart
interface class A {}
// but
class C extends A {}
// file_b.dart
// you cannot extend interface, only implement it
class B extends A {}
Interface Class
// file_a.dart
base class A {}
// file_b.dart
// class A can be only extended, not implemented
// moreover, the successor should be base class, too
base class B extends A {}
Base Class
// file_a.dart
final class A {}
// file_b.dart
// Class A cannot be nor extended nor implemented
class B extends A {}
Final Class
// file_a.dart
mixin class A {}
// file_b.dart
// Class A can be mixed in
class B with A {}
Mixin Class
// file_a.dart
sealed class A {}
class B extends A {}
class C extends A {}
// file_b.dart
// Class A can be nor extended nor implemented
// like final class, but it provides an exhaustive check
class D extends A {}
Sealed Class
It seems we are bending some of these rules, right? That's because these restrictions only apply outside of the library (file). And now, we have the cherry on top - Sealed Classes and Pattern Matching!
Sealed Classes & Pattern Matching
Exhaustive Type Checking is like the ultimate spell check but for your code. It ensures that you've covered all possible scenarios and left no possibility unchecked. Actually, this was already implemented to enums, but Dart 3.0 brings this to sealed classes.
final hello = Hello.hello;
switch (hello) {
case Hello.hello:
print('Hello');
break;
case Hello.world:
print('World');
break;
}
Enum exhaustiveness check
Sealed Classes
And now, let's see this in action with sealed classes:
sealed class A {}
class B extends A {}
class C extends A {}
void main(List<String> args) {
final b = B();
final isC = switch (b) {
C() => true,
_ => false,
};
print(isC); // prints false
}
Sealed Classes & Exhaustive Check
So, what's the lowdown on this code? We've birthed a variable into existence and we're playing a little game of 'Guess Who'. If our variable 'b' turns out to be the mysterious 'C', we reward ourselves by setting 'isC' to true. If not, alas, 'isC' gets a thumbs down with false. But wait, there's more! Let's crank this up a notch, shall we?
sealed class A {}
class B extends A {}
class C extends A {
C(this.name);
final String name;
}
void main(List<String> args) {
final A b = C("Hello");
// you must include all successors or default case
final isC = switch (b) {
C c => c.name,
_ => "",
};
print(isC); // prints "Hello"
}
I reckon you've already got a taste of how this feature can be as potent as a shot of espresso.
Pattern Matching: Examples
I've cooked up a delightful smorgasbord of pattern matching examples for your coding palate. Feast your eyes on these!
void main(List<String> args) {
final json = {
'name': 'John',
'age': 30,
'address': 'New York',
};
if (json case {'name': 'Michael'}) {
print('The username is Michael!!!');
} else if (json case {'name': String name}) {
print('The username is $name'); // prints The username is John
}
}
New case syntax
void main(List<String> args) {
final number = 10;
final someEnv = false;
final result = switch (number) {
> 100 && < 1000 => 'Between 100 and 1000',
> 100 when number % 2 == 0 => 'Greater than 100 and even',
> 100 when someEnv => 'Greater than 100 and someEnv',
< 10 => 'Less than 10',
> 10 => 'Greater than 10',
_ => 'Number $number is equal to 10',
};
print(result); // prints Number 10 is equal to 10
}
Pattern Matching #1
void main(List<String> args) {
final diverseList = [1, 2, 'Hello', 'World', true, false];
for (var item in diverseList) {
if (item case int n) {
print('Integer: $n'); // prints only integers
}
}
}
Pattern Matching #2
void main(List<String> args) {
final json = {
'name': 'John',
'age': 30,
'address': 'New York',
};
final name = switch (json) {
{'name': var n} => n,
_ when json.isEmpty => throw Exception('Empty JSON'),
_ => throw Exception('No name found'),
};
print(name); // prints John
}
Pattern Matching #3
void main(List<String> args) {
final record = ("John", 30, "New York");
final ageIsHigher = switch (record) {
(String _, int age) when age > 30 => true,
_ => false,
};
print(ageIsHigher); // Prints false
}
Pattern Matching #4
As you can see, the shiny new pattern matching system is like our coding superhero, bestowing upon us the power to create super-efficient exhaustive checks. This kind of approach is like a handy multi-tool, ready to be whipped out for a variety of code conundrums.
Conclusion
Well, we've had a rollicking ride with Dart 3.0! It's been exciting, it's been fun, and there's more coming! So stay tuned for more updates, and remember, in the world of coding, always keep your sense of humor!