{"id":15415811,"url":"https://github.com/shilangyu/dart-variance-hack","last_synced_at":"2025-08-30T12:39:41.712Z","repository":{"id":207805982,"uuid":"720128295","full_name":"shilangyu/dart-variance-hack","owner":"shilangyu","description":"Small overview of subtyping and variance in Dart motivated by a real example.","archived":false,"fork":false,"pushed_at":"2023-11-17T17:21:01.000Z","size":8,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-30T12:39:41.142Z","etag":null,"topics":["contravariance","dart","variance"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shilangyu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-11-17T16:28:07.000Z","updated_at":"2025-06-11T07:01:35.000Z","dependencies_parsed_at":"2023-11-17T18:44:13.677Z","dependency_job_id":null,"html_url":"https://github.com/shilangyu/dart-variance-hack","commit_stats":null,"previous_names":["shilangyu/dart-variance-hack"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/shilangyu/dart-variance-hack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shilangyu%2Fdart-variance-hack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shilangyu%2Fdart-variance-hack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shilangyu%2Fdart-variance-hack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shilangyu%2Fdart-variance-hack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shilangyu","download_url":"https://codeload.github.com/shilangyu/dart-variance-hack/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shilangyu%2Fdart-variance-hack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272852282,"owners_count":25004054,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["contravariance","dart","variance"],"created_at":"2024-10-01T17:09:43.667Z","updated_at":"2025-08-30T12:39:41.694Z","avatar_url":"https://github.com/shilangyu.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hacking contravariance in Dart\n\n\u003cdetails\u003e\n\u003csummary\u003ePrerequisite knowledge\u003c/summary\u003e\n\n1. `T \u003c: U` denotes that `T` is a subtype of `U`. Similarly `T :\u003e U` means that `T` is a supertype of `U`.\n2. Variance defines how should subtyping behave in presence of type parameters.\n3. A top type is a type that is a supertype of all other types. A bottom type is a type that is the subtype of all other types.\n\n\u003c/details\u003e\n\nThe Dart programming language does not allow programmers to decide on the variance of type parameters. All generics are covariant leading to well-known issues when combined with subtyping:\n\n```dart\nList\u003cint\u003e list = [1, 2, 3];\n\nList\u003cObject\u003e newList = list; // ok because int \u003c: Object so therefore List\u003cint\u003e \u003c: List\u003cObject\u003e\n\nnewList.add('oops'); // runtime error\n```\n\nLanguages that allow to control variance typically have a mutable and immutable kind of a list with an invariant and covariant type argument respectively. In Dart there is no such thing, so nothing prevents us from these runtime errors. See [subtyping_test.dart](test/subtyping_test.dart) and [variance_test.dart](test/variance_test.dart) for an introduction to subtyping and variance in Dart.\n\nA popular example where lack of contravariance is detrimental is a consumer class:\n\n```dart\nclass Consumer\u003cT\u003e {\n  void handle(T sth) {\n    print('doing $sth');\n  }\n}\n```\n\nIf we were to store a list of consumers and later wanted to retrieve those consumers which can accept a type `R`, we would quickly realize it harder than it initially seemed. Naively one could filter this list by those consumers that are subtypes of `Consumer\u003cR\u003e`. Take for example the hierarchy `int \u003c: num \u003c: Object`. If we were to filter by `num` we would get all consumers such that `\u003c: Consumer\u003cnum\u003e`. But this is not correct: we cannot pass in a `num` to a consumer which expects an `int`, and yet `Consumer\u003cint\u003e \u003c: Consumer\u003cnum\u003e`. See [problem.dart](lib/problem.dart) for an example of the problem.\n\nIn fact we need the reverse relationship, we want all consumers such that `:\u003e Consumer\u003cnum\u003e`. Since there is nothing we can do with this problem at the declaration-site, we need to resort to hacking use-site variance. There is one type in Dart that can simulate contravariance: the function type. Arguments of a function are contravariant, so `void Function(num) \u003c: void Function(int)` because `num :\u003e int`. We can use this fact to achieve use-site variance. See [variance.dart](lib/variance.dart) to see how to achieve objects which are covariant, contravariant, and invariant.\n\nInstead of storing a list of consumers alone, we will pair each consumer with a \"tag\" that exhibits contravariance behavior. Then when looking for compatible consumers we will filter by that tag rather than the consumer type. See [solution.dart](lib/solution.dart) for implementation.\n\n---\n\nThe Dart language has had an experimental feature for [declaration-site variance specifiers](https://github.com/dart-lang/language/issues/524) for a while now. This solves all presented issues and moves the responsibility of correctly specifying variance to the type declaration. See [explicit_variance.dart](lib/explicit_variance.dart) for a preview of this feature (run with `--enable-experiment=variance`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshilangyu%2Fdart-variance-hack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshilangyu%2Fdart-variance-hack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshilangyu%2Fdart-variance-hack/lists"}