In a Dart package, why can importing one file as both `package:my_pkg/src/cache.dart` and `../lib/src/cache.dart` break type checks and duplicate a singleton?
answer
- library identity is its URI
- file: URI vs package: URI
- two copies of every top-level
- Cache is not a subtype of Cache
- avoid_relative_lib_imports
basics
~20 sDart identifies a library by its URI, so a package: import and a relative path into lib load the same file twice as two unrelated libraries, with separate classes and separate top-level variables. Type tests fail and singletons exist twice.
solid answer
~40 sDart keys a library by its **canonical URI**, not by the file on disk. `package:my_pkg/src/cache.dart` and a relative `../lib/src/cache.dart` from `test/` resolve to two different URIs (`package:` versus `file:`), so the file is loaded **twice** as two unrelated libraries. Each copy has its own `Cache` class and its own top-level and static variables. An object from one copy fails an `is Cache` check against the other, errors read like "'Cache' is not a subtype of 'Cache'", and a lazily created singleton exists twice. Effective Dart's rules: never write `/lib/` or use `../` to leave `lib`, and reach into `lib` from `test/`, `bin/` or `example/` with `package:` imports. Inside `lib`, relative imports are fine. The `avoid_relative_lib_imports` lint in `package:lints`' core set catches the bad form.
code
dart · 16 lines// test/cache_test.dart
// BAD: file: URI into lib, a second copy of cache.dart
// import '../lib/src/cache.dart';
// GOOD: same URI that lib/app.dart resolves to
import 'package:my_pkg/src/cache.dart';
import 'package:my_pkg/app.dart';
import 'package:test/test.dart';
import 'fakes.dart'; // relative within test/ is fine
void main() {
test('app uses the shared cache', () {
expect(App().cache, same(Cache.instance));
});
}go deeper
Remember the rule: from test/ or bin/, import your own lib files with package:, never with ../lib/.
Explain that library identity is the resolved URI, so a file: and a package: URI load two copies with separate classes and globals.
Recognise the symptoms, such as 'X is not a subtype of X' or a twice-built singleton, and enforce avoid_relative_lib_imports so it cannot recur.
Set import-style lints once for all packages so this class of test-only bug is prevented by tooling rather than rediscovered by each team.
## Libraries are identified by URI Every Dart file is a **library**, and the compiler identifies it by the **URI** it was imported through, after resolving relative paths against the importing file's URI. Two URIs mean two libraries, even when both point at the same bytes on disk. A `package:` URI such as `package:my_pkg/src/cache.dart` is resolved through the package configuration that `dart pub get` writes, and it always refers to a path **inside the package's `lib/` directory**. A relative import is resolved against the URI of the file that contains it: - inside `lib/app.dart`, which was itself loaded as `package:my_pkg/app.dart`, the import `src/cache.dart` resolves to `package:my_pkg/src/cache.dart`; - inside `test/cache_test.dart`, loaded as a `file:` URI, the import `../lib/src/cache.dart` resolves to a `file:` URI. Effective Dart states the result plainly: Dart thinks those are imports of **two completely unrelated libraries**. ## What breaks when a library loads twice Each loaded copy gets its own declarations and its own storage: | Symptom | Cause | |---|---| | `is Cache` returns false for a real `Cache` | two distinct `Cache` classes exist | | error text like "'Cache' is not a subtype of 'Cache'" | the value's class comes from the other copy | | a singleton or registry initialised twice | top-level and static variables are per library | | an `enum` value fails `==` or a `switch` | two enum types with identical names | The bug is hard to see because the names print identically, and it usually appears only in tests or tools, where files outside `lib` import files inside it. ## The rules that prevent it Effective Dart gives two rules, both about crossing the `lib` boundary: 1. **Don't use `/lib/` in an import path, and don't use `../` to escape `lib`.** A file outside `lib` (in `test/`, `bin/`, `tool/`, `example/`) reaches into `lib` with a `package:` import. 2. **A file inside `lib` never reaches out** of `lib` with a relative path. When an import stays on one side of the boundary, Effective Dart **prefers relative paths** because they are shorter: `lib/src/utils.dart` imports `../api.dart`, and `test/api_test.dart` imports a sibling `test_utils.dart` relatively. Inside `lib`, relative and `package:` imports resolve to the same `package:` URI, so mixing them there is harmless; the damage comes only from a `file:` URI that points into `lib`. ## Tooling - `avoid_relative_lib_imports` is in `package:lints`' **core** set, so `flutter_lints` users already get a diagnostic for `import '../lib/api.dart';`. - `prefer_relative_imports` is an opt-in lint encoding the "relative inside lib" preference; it is not in the core or recommended sets. - The dart.dev package-layout page adds that, for web development builds, implementation files belong under `lib/src` and a package should avoid importing its own `package:<name>/src/...` paths. ## In a Flutter app A Flutter app is a package too: widget tests under `test/` import `package:my_app/main.dart` or `package:my_app/src/...`, never `../lib/main.dart`. A duplicated library shows up as a type lookup or `is` check that inexplicably fails only in tests.
- In a Dart package, is it a problem to mix relative and `package:` imports between two files that are both inside `lib/`?No for identity: a relative import inside a file loaded as `package:my_pkg/...` resolves to a `package:` URI, so both spellings name the same library. It is a consistency question; Effective Dart prefers relative imports there because they are shorter.
- How would you confirm that a failing Dart `is` check is caused by a duplicated library?Search the importers of that file for relative paths containing `lib/` or escaping it with `../`, typically in `test/` or `tool/`. Enabling `avoid_relative_lib_imports` makes the analyzer list them; switching those imports to `package:` and re-running should make the check pass.
saying these in an interview costs you the question
- Dart deduplicates libraries by their file path on disk
- Relative imports inside lib/ cause duplicate libraries
- Tests must import lib files relatively to see src/
- package: URIs are only for other people's packages
- Writing package:my_pkg/lib/api.dart is the correct spelling