[Breaking Change Request] Declare return types of Uint8List

Current state

The following methods all return Uint8List, yet they are only declared to return List<int>:

  • Utf8Codec.encode()
  • BytesBuilder.takeBytes()
  • BytesBuilder.toBytes()
  • File.readAsBytes()
  • File.readAsBytesSync()
  • RandomAccessFile.read()
  • RandomAccessFile.readSync()
  • Uint8List.sublist()

Relatedly, the following sublist() methods return sublists of the same type as
the source list, yet they only declare that they return the more generic type (e.g. List<int>, List<float>, or List<double>):

  • Int8List
  • Uint8ClampedList
  • Int16List
  • Uint16List
  • Int32List
  • Uint32List
  • Int64List
  • Uint64List
  • Float32List
  • Float64List
  • Float32x4List
  • Int32x4List
  • Float64x2List

Intended change

This issues proposes to update the API of the aforementioned methods to declare the return value of the more specific return type (e.g. Uint8List rather than List<int>).

Rationale

  1. Better type safety

    Callers would like to statically prove that you can obtain a ByteBuffer from the result of these API calls.

  2. The current API/implementation combo encourages bad practices.

    There are two dangers with an API saying it returns List<int> and always returning Uint8List:

    1. People do roundabout things to guarantee that the result is a Uint8List (such as defensively wrapping the result in a Uint8List), which causes unnecessary overhead.
    2. People start to depend on the result being a Uint8List and automatically performing a contravariant cast (which is already happening in Flutter and elsewhere) — and if anyone new implements the interface and returns something other than a Uint8List, code breaks.
  3. To match the documentation

    Utf8Encoder and Base64Decoder.convert, for instance, already document that they return Uint8List.

See also:

Expected impact

On callers

Callers of these APIs will not be impacted at all since the new return types are subtypes of the existing return types (and moreover, the return values will be the exact same values).

On implementations of the interfaces

Libraries that are implementing the following interfaces will be broken because they will no longer be implementing the interface:

  • Utf8Encoder
  • BytesBuilder
  • File
  • RandomAccessFile
  • Int8List
  • Uint8List
  • Uint8ClampedList
  • Int16List
  • Uint16List
  • Int32List
  • Uint32List
  • Int64List
  • Uint64List
  • Float32List
  • Float64List
  • Float32x4List
  • Int32x4List
  • Float64x2List

This includes (but is not limited to) some well-known packages, such as package:typed_data and package:file.

Steps for mitigation

For known affected packages, the plan will be to issue patch releases to those packages that tighten the SDK constraints to declare that the current version of the package is not compatible with an SDK version greater than the current dev version. Then, once the change lands, we’ll update the affected packages to implement the new API and loosen their SDK constraints once again.

Author: Fantashit

1 thought on “[Breaking Change Request] Declare return types of Uint8List

  1. I just hit a break on a private project, where I had response.transform(utf8.decoder).join("") for an HTTP request. It runs without issues on 2.4.0, but fails on a locally built SDK (2.5.0-edge). It doesn’t use any packages.

    I agree that the change is an improvement for some use-cases, but it’s also a hard breaking change for other naturally occurring use-cases.
    I think we should consider very hard whether this breaking change is really worth the pain for our users, because I don’t think I’ll be the only one affected.

    Our breaking change policy says that we make such breaking changes:

    If we deem that there is a very large benefit to changing current behavior, we may choose to do so after careful consideration of the associated impact of the change.

    I’m not sure we did a proper re-consideration of this tradeoff when it became clear that the breakage was more widespread than anticipated. That could very well tip the balance towards not doing the change.

    The feature hasn’t launched yet, so we can still decide to pull the plug on it.
    I know that it would be annoying to roll the change back, especially after doing all this work, but the only thing that matters is end-user impact.

    So, I humbly suggest that we do one more evaluation of the cost/benefit of this change before deciding to lauch it.

Comments are closed.