3 ms·
> As I understand it this is because crates are compiled as one compilation unit, and they have to be because circular dependencies are allowed Rust allows cir
by kibwen 1y ago
> As I understand it this is because crates are compiled as one compilation unit, and they have to be because circular dependencies are allowed
Rust allows circular dependencies between modules within a single compilation unit, but crates can't be circular dependencies.
> I expect most code doesn't actually have circular dependencies
Not true, most code does have benign circular dependencies, though it's not common to realize this. For example, consider any module that contains a `test` submodule, that's a circular dependency, because the `test` submodule imports items from the parent (it has to, because it wants to test them), but also the parent can refer to the submodule, because that's how modules and submodules fundamentally work. To eliminate the circular dependency you would need to have all test functions within every compilation unit defined within the root of the crate (not in a submodule off of the root; literally defined in the root namespace itself).
- IshKebab 1y ago> Rust allows circular dependencies between modules within a single compilation unit, but crates can't be circular dependencies. Yes, that's what I was saying. > that's a circular dependency, because the `test` submodule imports items from the parent This isn't a circular dependency because the parent doesn't import anything from the test module. It would mean your compilation unit might have to be a subset of a module, but I think that's fine?
- kibwen 1y agoGenerally the way that you would implement a restriction against circular dependencies is to prevent children from importing from their parents, full stop. The more elaborate analysis that you propose would work, technically, but it would only work for tests (if items can't be reached from the crate root, that's another way of saying that the code in question is dead and unreachable; tests only get around this because the test runner is a weird alternative entry point). You'd still be unable to, for example, have a submodule that defines a trait, and then a sibling submodule that defines a type that imports that trait; you'd need to move either the trait or the type to a shared root. To illustrate, this means that anything in the stdlib that implements std::iter::Iterator would need to be defined in a submodule of std::iter (or vice versa). And then repeat that process for every other trait: std::convert::From, std::ops::Drop, std::io::Read...