Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dart:io ServerSocket example and/or implementation is wrong #53344

Closed
james-d-hasselman opened this issue Aug 24, 2023 · 2 comments
Closed

dart:io ServerSocket example and/or implementation is wrong #53344

james-d-hasselman opened this issue Aug 24, 2023 · 2 comments

Comments

@james-d-hasselman
Copy link

General info

  • Dart 3.0.6 (stable) (Tue Jul 11 18:49:07 2023 +0000) on "linux_x64"
  • on linux / Linux 6.4.9-1-default
  • locale is C.UTF-8

Project info

  • sdk constraint: '^3.0.6'
  • dependencies:
  • dev_dependencies: lints, test

Process info

Memory CPU Elapsed time Command line
331 MB 0.5% 01:05:46 dart language-server --protocol=lsp --client-id=VS-Code --client-version=3.70.0

The Issue

The documentation for ServerSocket shows the basic example:

ServerSocket.bind('127.0.0.1', 4041)
  .then((serverSocket) {
    serverSocket.listen((socket) {
      socket.transform(utf8.decoder).listen(print);
    });
  });

Attempting to compile and run this code produces the following error:

Failed to build server:server:
bin/server.dart:7:29: Error: The argument type 'Utf8Decoder' can't be assigned to the parameter type 'StreamTransformer<Uint8List, dynamic>'.
 - 'Utf8Decoder' is from 'dart:convert'.
 - 'StreamTransformer' is from 'dart:async'.
 - 'Uint8List' is from 'dart:typed_data'.
      socket.transform(utf8.decoder).listen(print);

Tracing the documentation for Utf8Decoder, it is a StreamTransformer<List<int>, String>. The documentation for Socket transform has the signature transform<S>(StreamTransformer<Uint8List, S> streamTransformer) → Stream<S>. Uint8List is a subtype of List<int>.

Dart suggests the following cast utf8.decoder as StreamTransformer<Uint8List, dynamic> which satisfies the static type checker, but produces the following stack trace (excerpt) at runtime:

Unhandled exception:
type 'Utf8Decoder' is not a subtype of type 'StreamTransformer<Uint8List, dynamic>' in type cast

This is correct, because StreamTransformer<Uint8List, dyamic> is not a subtype of StreamTransformer<List<int>, String> rather it is the other way around.

If I change the code to the following, then it works fine even though Dart suggests the cast to List is unnecessary which it is not (or at least I couldn't figure it out):

serverSocket.listen((socket) {
      socket
          .map(
            (event) => event as List<int>,
          )
          .transform(utf8.decoder)
          .listen(print);
    });

Documentation References

dart:io
Utf8Decoder
Uint8List
Socket

@lrhn
Copy link
Member

lrhn commented Aug 25, 2023

The problem is that StreamTransformer is a class, and classes are covariant in their type parameters.
The class is basically:

abstract class StreamTransformer<S, T> {
  Stream<T> bind(Stream<S> source);
}

It really should have been a function, Stream<T> Function(Stream<S>), which is contravariant in S.

(Today the class also has a cast method, but that most likely wouldn't have been necessary if it had just been a function type to begin with.)

So, this is working as well as possible with the current API, and won't get better unless we introduce contravariant type variables. (Or we can introduce an extension method on Stream<T>: Stream<R> mapStream<R>(Stream<R> Function(Stream<S>) convert) => convert(this);, so you can do .transform(utf8.decoder.bind) properly contravariantly.

But if we do get contravariant type parameters, StreamTransformer is top of the list of candidates.

@lrhn lrhn closed this as completed Aug 25, 2023
@james-d-hasselman
Copy link
Author

Can the dart:io library documentation be updated to show this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants