Live data from Hacker News

Rewriting Rust

josephg.com

311–320 of 410 posts

Re: Rewriting Rust

#312

> The rust RFC process is a graveyard of good ideas. I actually have quite an opposite view: I think the Rust core team is 100% correct to make it very hard to add new "features" to the PL, in order to prevent the "language surface" from being bloated, inconsistent and unpredictable. I've seen this happen before: I started out as a Swift fan, even though I have been working with Objective-C++ for years, considered it…

It's easier to write RFCs than to implement them, and there are more people who can write RFCs. At any popularity level, you have more of the former. Therefore, an RFC queue will always look like a graveyard of good ideas, even if 100% of the queued ideas are being accepted and eventually worked on, simply due to the in/out differential rate.

If you want an edge over the people who are writing RFCs, don't write an RFC. Write a complete, production-ready implementation of your idea, with documentation and test cases, which can be cleanly merged into the tree.

Re: Rewriting Rust

#313

Earlier quoted context omitted.

> In fact, I think that the current state of async Rust is the best implementation of async in any language. Hahahaha hard disagree. Last year I implemented the braid protocol (a custom streaming protocol using HTTP) in javascript in less than an hour and about 30 lines of code. Then I spent 2 weeks trying to do the same thing in rust - writing hundreds of lines of code in the process and I couldn't get it to work. E…

> Then I spent 2 weeks trying to do the same thing in rust… Eventually I gave up. In my experience, that kind of difference boils down to a combination of three things. - Comparing apples and oranges. For example, Box makes pinning trivial (you can just move in and out of Pin no problem), but oftentimes people new to Rust try to prematurely optimise and eliminate a single pointer lookup. If that's the case, were you…

> - Comparing apples and oranges. For example, Box makes pinning trivial (you can just move in and out of Pin no problem), but oftentimes people new to Rust try to prematurely optimise and eliminate a single pointer lookup. If that's the case, were you really writing the same thing in JS and in Rust?

Pointer lookups are cheap-ish, but allocating can be extremely expensive if you do it everywhere. I've seen plenty of lazy, allocation & clone heavy rust code end up running much slower than the equivalent javascript. I assume for this reason.

But in this case, I couldn't get it working even when putting Box all over the place.

> What would happen in your JS implementation if two streams were awaited concurrently, one received a message, and the other had to be cancelled? What if one threw an exception? In Rust, you're forced to think about those things from the start. In JS, you're coding the happy path.

I implemented error handling in the javascript code. That was easy - since async generators in javascript support try-catch. Javascript doesn't support concurrent execution - so that problem doesn't exist there.

Did multithreading contribute to javascript being easier to write than rust? Who cares? I had a problem to solve, and javascript made that trivial. Rust made it a total nightmare.

I didn't know about the stream wrappers when I started coding this up. That was how I eventually found an the answer to this problem: I read that code then adapted their approach.

And by the way, have you read the code in those wrappers? Its wild how they glue manual Future implementations and async functions together (with some clever Boxes) to make it work. It blew my mind how complex this code needs to be in order for it to work at all.

> Over two years of writing embedded, web, and CLI rust I didn't have to write a raw future once.

I'm happy for you, and I wish I had the same experience. Streams are bread and butter for my work (CRDTs, distributed systems and collaborative editing). And at this rate? Proper support for streams in rust is probably a decade away.

Re: Rewriting Rust

#314
RE: Rust's pacing

I've had a lot of talks with my management about that. For context, I'm on the Cargo team and have authored 11 RFCs (10 approved, 1 pending).

I feel like a lot of the pacing feels slow because:

- As the project matures, polishing whats there takes up a lot of effort

- Conversely, hitting local maximas where things are "just good enough" that individuals and companies don't feel the need to put effort to doing the last leg of work.

- Lack of coordinated teams (formerly Mozilla) doubling down on an idea to hash it out. Hopefully [Project Goals](https://rust-lang.github.io/rfcs/3614-project-goals.html) will help a little in this direction.

- As the project has grown, we've specialized a lot more, making it harder to develop a cross-team feature. It takes finesse to recruit someone from another team to help you finish out a cross-team feature. It also doesn't help we've not done a good job developing the cross-team communication channels to make up for this specialization. Again, Project Goals are trying to improve this. In-person conferences starting back up has also been a big help.

As for RFCs, we've been moving in the direction of choosing the level of process thats appropriate for a decision. Unsure how something will look? You just need approval from 2 members of the relevant team to start a nightly only experiment to flesh out the idea in preparation for an RFC. In Cargo, many decisions don't need wide input and are just team votes on an Issue. RFCs drag out when their isn't a someone from the team shepherding it through the process, the RFC covers too much and needs to be shrunk to better focus the conversation, too much is unknown and instead an experiment is needed, or its cross-team and you need to know how to navigate the process to get the vote done (we want to improve this). As for things being approved but not completed, thats a "we need more help" problem usually.

Re: Rewriting Rust

#315
These are actually great and I would gladly welcome these changes if they were implemented in stable.

My wishlist:

* allow const fns in traits

* allow the usage of traits in const exprs. This would allow things like using iterators and From impls in const exprs, which right now is a huge limitation.

* allow defining associated type defaults in traits. This can already be worked around using macros somewhat effectively (see my supertrait crate) but real support would be preferable.

* allow eager expanding of proc macro and attribute macro input, perhaps by opting in with something like `#[proc_macro::expand(tokens)]` on the macro definition. Several core "macros" already take advantage of eager expansion, we peasants simply aren't allowed to write that sort of thing. As a side note, eager expansion is already possible for proc and attribute macros designed to work _within proc macro crates_, for example this which I believe is the first time this behavior was seen in the wild: https://github.com/paritytech/substrate/blob/0cbea5805e0f4ed...

* give build.rs full access to the arguments that were passed to cargo for the current build. Right now we can't even tell if it is a `cargo build` or a `cargo doc` or a `cargo test` and this ruins all sorts of opportunities to do useful things with build scripts

* we really need a `[doc-dependencies]` section in `Cargo.toml`

* give proc macros reliable access to the span / module / path of the macro invocation. Right now there are all sorts of projects that hack around this anyway by attempting to locate the invocation in the file system which is a terrible pattern.

* allow creating custom inner attributes. Right now core has plenty of inner attributes like `#![cfg(..)]` etc, and we have the syntax to define these, we simply aren't allowed to use custom attribute macros in that position

* revamp how proc macro crates are defined: remove the `lib.proc-macro = true` restriction, allowing any crate to export proc macros. Facilitate this by adding a `[proc-macro-dependencies]` section to `Cargo.toml` that separately handles proc-macro-specific dependencies. Proc macros themselves would have access to regular `[dependencies]` as well as `[proc-macro-dependencies]`, allowing proc macro crates to optionally export their parsing logic in case other proc macro crates wish to use this logic. This would also unblock allowing the use of the `$crate` keyword within proc macro expansions, solving the age old problem of "how do I make my proc macro reliably refer to a path from my crate when it is used downstream?"

* change macro_rules such that `#[macro_export]` exports the macro as an item at the current path so we can escape from this ridiculousness. Still allow the old "it exports from the root of the current crate" behavior, just deprecate it.

Re: Rewriting Rust

#316
post #185

Earlier quoted context omitted.

Edit: replied to wrong person.

Huh? > It's throwing the baby and bathwater into lava. Is it really so controversial to want to be able to limit the access that utility crates like humansize or serde have to make arbitrary syscalls on my computer? Seems to me like we could get pretty far with just compile-time checks - and that would have no impact whatsoever on the compiled code (or its performance). I don't understand your criticism.

I thought you wanted to prevent transitive dependencies. For sandboxing crates, as JoshTriplett said it's another can of worms.

Re: Rewriting Rust

#317

> Now, there are issue threads like this, in which 25 smart, well meaning people spent 2 years and over 200 comments trying to figure out how to improve Mutex. And as far as I can tell, in the end they more or less gave up. The author of the linked comment did extensive analysis on the synchronization primitives in various languages, then rewrote Rust's synchronization primitives like Mutex and RwLock on every major…

> Sandboxing against malicious crates is an out-of-scope problem. You can't do this at the language level; you need some combination of a verifier and runtime sandbox. WebAssembly components are a much more likely solution here. But there's lots of interest in having capabilities for other reasons, for things like "what allocator should I use" or "what async runtime should I use" or "can I assume the platform is 64-bit" or similar. And we do want sandboxing of things like proc macros, not because of malice but to allow accurate caching that knows everything the proc macro depends on - with a sandbox, you know (for instance) exactly what files the proc macro read, so you can avoid re-running it if those files haven't changed.

We've had a lot of talk about sandboxing of proc-macros and build scripts. Of course, more declarative macros, delegating `-sys` crate logic to a shared library, and `cfg(version)` / `cfg(accessible)` will remove a lot of the need for user versions of these. However, that all ignores runtime. The more I think about it, the more cackle's "ACLs" [0] seem like the way to go as a way for extensible tracking of operations and auditing their use in your dependency tree, whether through a proc-macro, a build script, or runtime code.

I heard that `cargo-redpen` is developing into a tool to audit calls though I'm imagining something higher level like cackle.

[0]: https://github.com/cackle-rs/cackle

Re: Rewriting Rust

#318
post #290

Earlier quoted context omitted.

There's probably a similar amount of code in the execution path, but the Rust ecosystem reliance on dependencies means that you're pulling in vast amounts of code that doesn't make it to your final application. A C++ library author is much more likely to just implement a small feature themselves rather than look for another 3rd party library for it. Adding dependencies to your library is a more involved and manual pr…

> A C++ library author is much more likely to just implement a small feature themselves rather than look for another 3rd party library for it. This is conflating Javascript and Rust. Unlike Javascript, Rust does not have a culture of "microdependencies". Crates that get pulled in tend to be providing quite a bit more than "just a small feature", and reimplementing them from scratch every time would be needlessly redu…

>Rust does not have a culture of "microdependencies"

It absolutely does by the C/C++ standards. Last time I checked the zed editor had 1000+ dependencies. That amount of crates usually results in at least 300-400 separately maintained projects by running 'cargo supply-chain'. This is an absurd number.

Re: Rewriting Rust

#319

Earlier quoted context omitted.

> Then I spent 2 weeks trying to do the same thing in rust… Eventually I gave up. In my experience, that kind of difference boils down to a combination of three things. - Comparing apples and oranges. For example, Box makes pinning trivial (you can just move in and out of Pin no problem), but oftentimes people new to Rust try to prematurely optimise and eliminate a single pointer lookup. If that's the case, were you…

> - Comparing apples and oranges. For example, Box makes pinning trivial (you can just move in and out of Pin no problem), but oftentimes people new to Rust try to prematurely optimise and eliminate a single pointer lookup. If that's the case, were you really writing the same thing in JS and in Rust? Pointer lookups are cheap-ish, but allocating can be extremely expensive if you do it everywhere. I've seen plenty of…

Every single JS future is boxed. Moreover, they aren't just boxed, they are often backed by a hashmap (which may or may not be optimised away by the JIT). Elaborate allocation-free async is not an apple-to-apples comparison.

JS does support concurrent execution, Promise.all is an example. Without it, JS async would make little sense. The problem very much exists there, and try-catch is only a surface-level answer. As you can see here [1], the interaction of cancellation and async in JS is at least just as (or more) complex than in Rust.

By the way, multithreading has little to do with Pin. I presume you're thinking of Send bounds.

"To work at all" is very dismissive. Those wrappers are complex, but very well abstracted, well defined, and robust, the complexity is essential. Again, look at [1], JS async is hardly less complex, but also much more vague and ill-defined.

[1]: https://github.com/whatwg/streams/issues/1255

Re: Rewriting Rust

#320
The drama around rust leadership and also in kernel has me more spooked tbh. It’s more of a threat to the long term viability of the lang. Flaws in a language are to some extent expected and can be worked around.

Languages like C++ and python are wildly successful and don’t think anyone would call them perfect.

The dependence point is valid but not sure that is easily solvable in general. Doesn’t seem like a rust issue. See npm and python pip - blind trust is par for the course except in very rigorous environments

Post reply on HN