Difference between yield and yield* in Dart
Short answer
yield
returns values from an Iterable or a Stream.yield*
is used to recursively call its Iterable or Stream function.
Examples
Let's look an some examples from the Generator Functions - Flutter in Focus video. We'll just look at the Iterable example, but it's similar for Streams.
Form 1
Here is an Iterable that counts from start
to finish
.
Iterable<int> getRange(int start, int finish) sync* {
for (int i = start; i <= finish; i++) {
yield i;
}
}
The yield
keyword returns the next value on each iteration.
Form 2
Now let's refactor that function to be recursive. On the outside it still does the same thing as before:
Iterable<int> getRange(int start, int finish) sync* {
if (start <= finish) {
yield start;
for (final val in getRange(start + 1, finish)) {
yield val;
}
}
}
This works but it's hard to read and not very efficient because of the loop.
Form 3
Now let's refactor it again using yield*
(pronounced "yield star"):
Iterable<int> getRange(int start, int finish) sync* {
if (start <= finish) {
yield start;
yield* getRange(start + 1, finish);
}
}
It's still recursive, but now it's easier to read and is more efficient.
I have created a dart pad link to help people experiment:
Yield* is used to yield a whole iterable one value at a time with out using a loop.
These 2 functions do exactly the same thing, generates an iterable based on the start and finish values.
Iterable<int> getRangeIteration(int start, int finish) sync* {
for(int i = start; i<= finish; i++){
yield i;
}
}
Iterable<int> getRangeRecursive(int start, int finish) sync* {
if (start <= finish) {
yield start;
yield* getRangeRecursive(start + 1, finish);
}
}
In the first implementation (yield i) would imply type Iterable which matches the function's return type.
now if in the second implementation instead of
yield* getRangeRecursive(start + 1, finish);
if we did
yield getRangeRecursive(start + 1, finish);
we will get a compiler error:
The type 'Iterable<Iterable<int>>' implied by the 'yield' expression must be assignable to 'Iterable<int>'.
as you can see the yield wraps the Iterable with another Iterable<>, which makes the type Iterable<Iterable>. Which does not match the return type of the function.
If we have to do recursion without using yield* we will have to do this:
Iterable<int> getRangeRecursiveWithOutYieldStar(int start, int finish) sync* {
if (start <= finish) {
yield start;
for (final val in getRangeRecursiveWithOutYieldStar(start + 1, finish)){
yield val;
}
}
}
Which is messy and inefficient.
So I feel like in my opinion yield* flattens another generator function.
A few good resources: Generator Functions - Flutter in Focus video
Medium article: What are sync*, async*, yield and yield* in Dart?
yield
It is used to emit values from a generator either async or sync.
Example:
Stream<int> str(int n) async* {
for (var i = 1; i <= n; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
void main() {
str(3).forEach(print);
}
Output:
1
2
3
yield*
It delegates the call to another generator and after that generator stops producing the values, it resumes generating its own values.
Example:
Stream<int> str(int n) async* {
if (n > 0) {
await Future.delayed(Duration(seconds: 1));
yield n;
yield* str(n - 1);
}
}
void main() {
str(3).forEach(print);
}
Output:
3
2
1