Search code examples
dartreturn-typetype-safety

Seeking a safe translation of a Python dict as a return type to Dart


Once in a while we all need to quickly return multiple values from a function, and look for a way to create a new type on the fly.

In Python I can return a tuple

def get_trio1():
    return (True, 23, "no")


(_, first1, second1) = get_trio1()
print(first1)
print(second1)

ignore one of the values, and retrieve both of the other two on the fly in one assignment.

I can likewise return an array.

def get_trio2():
    return [True, 23, "no"]


[_, first2, second2] = get_trio2()
print(first2)
print(second2)

But both of these are brittle. If I edit the code to add a value, particularly if it's within the three already defined, the revised code could fail silently.

Which is why the nicest solution is to create a dict on the fly.

def get_trio3():
    return {"b": True, "i": 23, "s": "no"}


r = get_trio3()
print(r["i"])
print(r["s"])

The use of named members means that maintaining the code is considerably safer.

What is the closest I can do to get the same safety in Dart? Is defining a class for the return type necessary?

In case it matters, the context is avoiding List<List<dynamic>> when returning a future.

Future<List<dynamic>> loadAsset() async =>
  return await Future.wait([
    rootBundle.loadString('assets/file1.txt'),
    rootBundle.loadString('assets/file2.txt'),
  ]);

Update

Using Stephen's answer for a future introduces a problem. Future.wait is hardwired to use an array Iterable.

Future<Map<String, dynamic>> loadAsset() async =>
  return await Future.wait({
      "first": rootBundle.loadString('assets/file1.txt'),
      "second": rootBundle.loadString('assets/file2.txt'),
});

Solution

  • Your loadAsset function returns a Future<List<dynamic>> because that's how you declared it. You could have declared it to return a Future<List<String>> instead.

    Future.wait is hardwired to use an array.

    Especially since Dart is a statically-typed language, you can't really expect it to take both a List and some Map with your custom semantics. You could write your own version:

    Future<Map<String, T>> myFutureWait<T>(Map<String, Future<T>> futuresMap) async {
      var keys = futuresMap.keys.toList();
      var values = futuresMap.values.toList();
      var results = await Future.wait<T>(values);
      return Map.fromIterables(keys, results);
    }