5 ms·
My biggest issue with rust after two years is just as you highlight: the mod/crate divide is bad! I want it to be easier to have more crates. The overhead of c
by vvern 1y ago
My biggest issue with rust after two years is just as you highlight: the mod/crate divide is bad!
I want it to be easier to have more crates. The overhead of converting a module tree into a new crate is high. Modules get to have hierarchy, but crates end up being flat. Some of this is a direct result of the flat crate namespace.
A lot of the toil ends up coming from the need to muck with toml files and the fact that rust-analyzer can’t do it for me. I want to have refactoring tools to turn module trees into crates easily.
I feel like when I want to do that, I have to play this game of copying files then playing whack-a-mole until I get all the dependencies right. I wish dependencies were expressed in the code files themselves a la go. I think go did a really nice job with the packaging and dependency structure. It’s what I miss most.
- movpasd 1y agoIt's a surprising choice that Rust made to have the unit of compilation and unit of distribution coincide. I say surprising, because one of the tacit design principles I've seen and really appreciated in Rust is the disaggregation of orthogonal features. For example, classical object-oriented programming uses classes both as an encapsulation boundary (where invariants are maintained and information is hidden) and a data boundary, whereas in Rust these are separated into the module system and structs separately. This allows for complex invariants cutting across types, whereas a private member of a class can only ever be accessed within that class, including by its siblings within a module. Another example is the trait object (dyn Trait), which allows the client of a trait to decide whether dynamic dispatch is necessary, instead of baking it into the specification of the type with virtual functions. Notice also the compositionality: if you do want to mandate dynamic dispatch, you can use the module system to either only ever issue trait objects, or opaquely hide one in a struct. So there is no loss of expressivity.
- vvern 1y agoHow would you compare that to, say, go? I think the unit of distribution in go is a module, and the unit of compilation is a package. That being said, by using `internal` packages and interfaces you can similarly create the same sort of opaque encapsulation.
- mamcx 1y agoHard agree. Is retrospect I think the model of Delphi, where you must assemble `manually` a `pkg` so you can export to the world should have been used instead. It also have solved the problem where you ended doing a lot of `public` not because the logic dictated it, but as only way to share across crates. It should have been all modules (even main.rs with mandatory `lib.rs` or whatever) and `crate` should have been a re-exported interface.
- steveklabnik 1y ago> Hard agree. Is retrospect I think the model of Delphi, where you must assemble `manually` a `pkg` so you can export to the world should have been used instead. Very very old Rust had "crate files" which were this https://github.com/rust-lang/rust/blob/a8eeec1dbd7e06bc811e55c641b6c282807997a5/src/test/run-pass/native.rc https://github.com/rust-lang/rust/blob/a8eeec1dbd7e06bc811e5... .rc standing for "rust crate" There's pros and cons here. I'm of two minds.
- steveklabnik 1y agoThe history here is very interesting, Rust went through a bunch of design iteration early, and then it just kinda sat around for a long time, and then made other choices that made modifying earlier choices harder. And then we did manage to have some significant change (for the good) in Rust 2018. Rust's users find the module system even more difficult than the borrow checker. I've tried to figure out why, and figure out how to explain it better, for years now. Never really cracked that nut. The modules chapter of TRPL is historically the least liked, even though I re-wrote it many times. I wonder if they've tried again lately, I should look into that. > Another example is the trait object (dyn Trait), which allows the client of a trait to decide whether dynamic dispatch is necessary, instead of baking it into the specification of the type with virtual functions. Here I'd disagree: this is separating the two features cleanly. Baking it into the type means you only get one choice. This is also how you can implement traits on foreign types so easily, which matters a lot.
- Wilfred 1y ago> Rust's users find the module system even more difficult than the borrow checker. I've tried to figure out why, and figure out how to explain it better, for years now. The module system in Rust is conceptually huge, and I feel it needs a 'Rust modules: the good parts' resource to guide people. (1) There are five different ways to use `pub`. That's pretty overwhelming, and in practice I almost never see `pub(in foo)` used. (2) It's possible to have nested modules in a single file, or across multiple files. I almost never see modules with braces, except `mod tests`. (3) It's possible to have either foo.rs or foo/mod.rs. It's also possible to have both foo.rs and foo/bar.rs, which feels inconsistent. (4) `use` order doesn't matter, which can make imports hard to reason about. Here's a silly example: use foo::bar; use bar::foo; (Huge fan of your writing, by the way!)
- steveklabnik 1y agoFull agree with 1, I do use 2 depending (if I'm making a tree of modules for organization, and a module only contains imports of other modules, I'll use the curly brace form to save the need of making a file), and I'm not sure why 4 makes it harder? Wouldn't it be more confusing if order mattered? maybe I need to see a full example :) Thank you!