0

The answers to this question:

Multiple lists share same reference

Made me wonder. Is there any difference in making a copy of a list by doing

list.toList()

or

[...list]

Does it do exactly the same "under the hood" or is one more efficient than the other? I would think that toList() would be the better one since the other method needs to unpack and pack the list again, but maybe the same happens in the first method.

Ivo
  • 18,659
  • 2
  • 23
  • 35

1 Answers1

3

This is not an official answer and I hope one with better knowledge can correct me if I am wrong.

In short, both should result in the same performance and behavior.

We can observe this using --disassemble with the Dart VM 2.17.5, where it looks like [...list] is being translated into List.of(list1). The List.of is defined here:

  @patch
  factory List.of(Iterable<E> elements, {bool growable: true}) {
    if (growable) {
      return _GrowableList.of(elements);
    } else {
      return _List.of(elements);
    }
  }

https://github.com/dart-lang/sdk/blob/9ad08f1c951ae8b5ee0387306f450afaccf4eaba/sdk/lib/_internal/vm/lib/array_patch.dart#L48-L55

Since we can only create growable lists with the [] syntax, then we follow the track into _GrowableList.of which is defined as:

  factory _GrowableList.of(Iterable<T> elements) {
    if (elements is _GrowableList) {
      return _GrowableList._ofGrowableList(unsafeCast(elements));
    }
    if (elements is _Array) {
      return _GrowableList._ofArray(unsafeCast(elements));
    }
    if (elements is EfficientLengthIterable) {
      return _GrowableList._ofEfficientLengthIterable(unsafeCast(elements));
    }
    return _GrowableList._ofOther(elements);
  }

https://github.com/dart-lang/sdk/blob/eb17dad60440daeb5998751db8311d82769cfb64/sdk/lib/_internal/vm/lib/growable_array.dart#L142-L153

So as we can see, Dart will try and check if the provided Iterable is actually some other specific type. The common thing here is that we prefer to know the final size of the created new List but Iterable cannot safely, by default, be checked for its length without iterating over all elements in the Iterable. So if we know the Iterable is actually a type where we safely can get the length, without any iteration, we should use that size.

The toList() method is implemented differently for different types of object in Dart and is therefore a way to have an efficient way to make a list from that object. E.g. if we have an _GrowableList, the toList() for that can be found here:

  List<T> toList({bool growable: true}) {
    // ...
    final length = this.length;
    if (growable) {
      if (length > 0) {
        final data = new _List(_adjustedCapacity(length));
        for (int i = 0; i < length; i++) {
          data[i] = this[i];
        }
        final result = new _GrowableList<T>._withData(data);
        result._setLength(length);
        return result;
      }
      return <T>[];
    } else {
      if (length > 0) {
        final list = new _List<T>(length);
        for (int i = 0; i < length; i++) {
          list[i] = this[i];
        }
        return list;
      }
      return List<T>.empty(growable: false);
    }
  }

https://github.com/dart-lang/sdk/blob/eb17dad60440daeb5998751db8311d82769cfb64/sdk/lib/_internal/vm/lib/growable_array.dart#L509-L540

And we can see in this implementation that since it is already a _GrowableList, it knows it can get the length efficiently and then uses that in its implementation for creating a new list based.

julemand101
  • 28,470
  • 5
  • 52
  • 48