4 ms·
I came to rust mostly from higher level languages, and I didn’t have as much of a hard time with lifetimes as I did with dynamic dispatch via trait objects. Lif
by mplanchard 4y ago
I came to rust mostly from higher level languages, and I didn’t have as much of a hard time with lifetimes as I did with dynamic dispatch via trait objects. Lifetimes can certainly get hairy, but it’s easy to get around them when you’re learning by cloning a value, wrapping it in an Arc, or whatever. I have had multiple times where I wanted to use dynamic dispatch and discovered after a few days of work that my idea wouldn’t work for one reason or another. Mostly this came down to some limitations on what trait objects can and can’t do, but it has mostly ceased to be a problem now that I understand those limitations better.
- KingOfCoders 4y agoYou didn't have a hard time with the borrow checker because you used the Rust GC? Was that the argument? Actually I left Rust and moved to Go although I love Rust much more because every struct ended up sprinkled with Arc.
- mplanchard 4y agoNo, there’s no argument. The statement was that lifetimes aren’t that hard, and that when you do get stuck there are escape hatches. The escape hatches are especially useful when you’re still learning. FWIW I write Rust professionally now, and in our entire codebase we have maybe four or five data structures containing Arcs, specifically where objects are spawned as Futures on other threads.
- KingOfCoders 4y agoThat would be a very interesting metric, could you grep|wc them and the locs?
- mplanchard 4y agoSure! Tokei says that we've got ~76k lines of Rust. I used ripgrep to find occurrences of `Arc::` (to exclude type signatures and only find constructors) and then just gave it a quick manual look to exclude tests and benchmarks. This leaves me with: - one data structure that contains general application state and contextual information, which is created at application startup and shared across all tasks/threads - one data structure for accumulating results that is shared across threads. This is an Arc<Mutex<u64, SomeEnum>>. The less common of the two enum variants is an Arc<BTreeMap>. - one data structure used to pass a database connection in to a context where it'll be used to spawn async tasks All told, we've only got four places in production paths where we construct a new Arc. One of those four is only called once in the life cycle of the application, while the others are called for any given invocation.
- KingOfCoders 4y agoYou're comment made my day, thanks for your work!
- mplanchard 4y agoHey no problem! I was glad my guess wasn’t too far off, and it was fun to go digging to check.
- pclmulqdq 4y agoCloning values and using Arc to wrap structs carry a huge performance costs. It's pretty easy to write correct Rust. It's a lot trickier to write fast Rust. That's where the learning curve started to get very steep for me.
- PoignardAzur 4y ago> and using Arc to wrap structs carry a huge performance costs Does it? It's like using shared_pointer, which some C++ codebases do everywhere for memory safety. And some languages like Swift even do it by default.
- estebank 4y agoIt really depends on what you're building whether that perf cost is problematic or not, and usually it isn't. If 90% of your code is clean Rust and the last 10% outside of the critical path is a straightforward clone or Arc, then I see no reason not to go that way.
- fbdab103 4y agoIs the performance difference meaningful? I write Python, so I suspect even poorly ported Rust will outperform artisanally crafted Python.
- pclmulqdq 4y agoYes. Most of the production use cases for Rust are aimed at C, C++, and Go users. In my past experience, even poorly-written Go tends to outperform poorly-written Rust for many tasks, not to mention C or C++. You're not exactly the target market if you're writing python.
- fbdab103 4y agoBut that is kind of my point. There are plenty of reasons to use Rust that have nothing to do with performance. My brief toe-dip into the Rust world loves the static types, good dependency management, and single executable deployment. That it is faster than my standard language is just the cherry on top.
- ninkendo 4y ago> Mostly this came down to some limitations on what trait objects can and can’t do, but it has mostly ceased to be a problem now that I understand those limitations better. Care to recount what some of those misunderstandings were? I’m casually interested in Rust but only really observe from afar, since most of my day job is Swift. Don’t get me wrong, Swift has some odd limitations around protocols (closest equivalent to traits) that may be similar, but I’m curious to see what some common pitfalls may be with Rust traits.
- mplanchard 4y agoI’m sorry I don’t have specific examples because it’s been a while, but IIRC generally the issues came from trying to mix compile-time dynamism with runtime dynamism. Things like depending on methods that made traits non-object-safe and so on. I think it can be quite confusing initially how traits are both the unit of compile-time generics AND can be used for dynamic dispatch at runtime, given that there are separate rules about what can be used in which context.