HN.zip

Two Years of Rust

56 points by todsacerdoti - 16 comments
vvern [3 hidden]5 mins 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 [3 hidden]5 mins ago
It'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.

apitman [3 hidden]5 mins ago
> The way I would summarize Rust is: it’s a better Go, or a faster Python

That's an interesting take. I feel like all three of these languages fit into pretty discrete lanes that the others don't. Python for quick hacking or scientific stuff, Go for web services and self-contained programs, Rust for portability (specifically sharing code as C ABI or WASM) and safety.

> It’s not hard to learn

I agree Rust is easy to learn. I've done it 4 or 5 times now.

ratmice [3 hidden]5 mins ago
> with cargo you praise the absences: there’s no gotchas, no footguns, no lore you have to learn in anger, no weirdness, no environment variables

Suppose the author doesn't use build.rs, which appears to have been composed of the listed things almost entirely.

rcxdude [3 hidden]5 mins ago
build.rs is a useful escape hatch for if you need to do something more complicated, but the nice thing about cargo is that for the most part the defaults work. Generally build.rs only comes in if you have to deal with C, C++ or some other external ecosystem. A pure rust crate basically never needs to touch it, across multiple platforms and build configurations.
ratmice [3 hidden]5 mins ago
Generally yes, but there are things like lalrpop which are pure rust, I personally fall within the pure rust build.rs user purview...
meltyness [3 hidden]5 mins ago
Nice to have when you still refuse to make learn
nusaru [3 hidden]5 mins ago
> Personally I didn’t have borrow checker problems, but that’s because before I started using Rust at work I’d designed and built my own borrow checker. I don’t know if that’s a scalable pedagogy.

Yeah... maybe not, but I can see this being a project in an undergraduate course.

Dowwie [3 hidden]5 mins ago
Use dependency injection and mock behaviors. This technique works in several programming languages, including Rust.

Rust has modules, crates and workspaces. To optimize builds, you'll eventually move shared resources to their own crate(s).

q3k [3 hidden]5 mins ago
> Error Handling

I've yet to see anyone demonstrate the elegance Rust error handling for anything but the simplest of cases. It's all fun and games and question marks... until you hit this:

    $ ./app
    called `Result::unwrap()` on an `Err` value: no such file or directory
And then you start investigating and it turns out that the error value comes from somewhere deep in an unknown callstack that got discard by the authors using '?' everywhere.

Yes, I know about anyhow and thiserror and eyre and ... ; point is none of this is ever shown in these 'look how elegant error handling is' posts. Come on, let's be a bit more honest with ourselves about Result<T, E> and '?' - it's not a full solution to error handling. After two years I'm sure you've hit this.

meltyness [3 hidden]5 mins ago
Gripes about the borrow checker I think would be cured with the following surprising fact, and interesting "problem solving" approach to the language's design:

In Rust there's at least 5 types of everything, in order of strength:

- Value / unqualified / "owned"

  - Generically, T

  - Optionally mutable
- Mutable Reference

  - &mut T

  - you can only have one of these for a given value
- Reference / Shared reference

  - &T

  - you can have arbitrarily many for a given value
- Raw constant pointer

  - *const T

  - you can have arbitrarily many, and they're not liveness checked
- Raw mutable pointer

  - *mut T

  - you can have arbitrarily many, and they're not liveness checked
Now I say at least because things get appreciably more complicated when you find yourself dealing with lifetimes which apply to "References", those are indeed themselves types, but ultimately represent a compiler-executed calculus regarding liveness relative to some Value.

They also can 'fan out' like a multiple-dereference pointer, but the tricky part is how the APIs for Types conform to these, for example;

Since there are 3 different types of things in a collection, then there are 3 different ways to iterate over them `iter()`, `iter_mut()`, `into_iter()` in increasing order of strength. Most of the breadth or early complexity arises from the urge to treat these as a distraction, rather than a fundamental aspect of systems code.

Crates / modules are a bit of a meme: https://www.reddit.com/r/rust/comments/ujry0b/media_how_to_c...

Bevy has done some work investigating build performance: https://bevyengine.org/learn/quick-start/getting-started/set...

nathan_douglas [3 hidden]5 mins ago
I feel like it's intuitive for me to think about this stuff as just a second type system rather than to think about the details of how the compiler works or how it'll function at runtime. A given value exists in a kind of superposition, and I pick the form I want it to collapse into (value, reference, mutable reference, etc) based on the tradeoffs I need at that moment. I don't know exactly why this is helpful, or if it's helpful (or even coherent) to anyone but me. It might also be damning me to some conceptual outer darkness where I'll hear the pounding of accursed drums and the wailing of hideous flutes forevermore.
williamcotton [3 hidden]5 mins ago
Please don't take this the wrong way, it's just the editor in me.

There's a typo at the end of the Error Handling section:

When you need to explicitly handle an error, you omit the question mark operator and use thw Result value directly.

ninetyninenine [3 hidden]5 mins ago
The mock example looked pointless.

IO can’t be unit tested hence why you mock it. But his code didn’t do anything but confirm his mock worked. He’s writing mocks and testing mocks.

The functionality he referenced is just inherently not unit testable. Again, If you try to mock it and test things you end up testing your mocked code. That’s it.

I’ve seen this strange testing philosophy pop up time and time again where test code misses a shit load of errors because it’s just confirming that the mocks work.

For this area you need to move to integration tests if you want to confirm it works. This comes with the pain of rewriting tests should the implementations change but testing just mocks isn’t solving this problem.

Your unit tests only really matter if you’re doing a lot of big algorithm stuff and not much IO. Mocking helps if you have some IO sprinkled into a unit computation. In the example he gave every operation was IO and every operation had to be mocked so wtf was he thinking to want to place that in a unit test?

Joker_vD [3 hidden]5 mins ago
> IO can’t be unit tested hence why you mock it.

Say, I have this module that uses a private MongoDB as a cache. Its unit tests spin up a standard MongoDB container and use it (and then tear it down). Are they still unit tests or should I start calling them "integration tests"?

ninetyninenine [3 hidden]5 mins ago
Integration.

Unit tests should just test code units. All the external stuff should be mocked or not tested.

The example in the post is a unit test.

It’s good to keep it separate as unit tests are really easy to run and less complicated and much faster. Integration tests are much more complicated and often sort of freeze your code as it locks in the implementation.