7 ms·
I write a decent amount of Rust and I find it productive, but I can see how it might be easy to get nerd-sniped trying to get rid of every last allocation. Ther
by ntoskrnl 4y ago
I write a decent amount of Rust and I find it productive, but I can see how it might be easy to get nerd-sniped trying to get rid of every last allocation. There's no shame in a Box/Arc. Remember that almost every language puts almost everything on the heap. Just allocate. It'll be fine. Really.
I set a rule for myself that I'll spend up to one minute trying to save an allocation. Beyond that it's not worth getting sidetracked.
- lumost 4y agoso something I wonder, in a typical language you can easily pass by reference between components and threads. Avoiding any allocations. In rust the unspoken rule seems to be to allocate, allocate, allocate - unless you are writing a special purpose library etc. Which makes me wonder, is rust actually faster than a GC'd language when doing heavy async work?
- lijogdfljk 4y agoFwiw i rarely allocate around these "issues" and i use all async. I think your comment could be tweaked to say: > In rust the unspoken rule seems to be to allocate, allocate, allocate when you run into a lifetime issue Lifetimes work fine with async, but some types of lifetimes can be problematic, for sure.
- bluejekyll 4y agoThis isn’t quite accurate. It’s generally really easy in Rust to pass things by reference, and even inner async fns, this is easy. The issue with async and Futures, is that sometimes you need to capture the future and then pass that to something else to execute. In that context, shared references are hard, and just clone, or arc box like mentioned.
- coder543 4y agoI think a lot of Rust users would argue they don’t even care much about performance. They just enjoy all of the correctness guarantees the compiler can enforce, as well as how ergonomic the language can feel. Being able to deploy a single static binary, and having an easy to use build tool and package manager are significant bonuses as well. Rust makes really hard problems easier when you know the compiler has your back. Rust gives you the tools to write very high performance code, but it doesn’t have to be about that. I’ve had to point out to people quite a few times that garbage collectors can improve performance… especially compared to naive implementations of manual memory management. GCs are not just a tool for lazy programmers. GCs can make allocation incredibly fast, and you get to defer cleanup work to another thread(s), which means less work in the critical path. Removing work from the critical path is how you make software faster. Every tool has tradeoffs, and GCs are a tool. GCs often use more memory as a tradeoff. I like Rust well enough, but I do wish we had a language that combined the ergonomics of Rust with the dead simple concurrency model of Go/Erlang. I haven’t tried it, but Luantic looks promising: https://github.com/lunatic-solutions/lunatic https://github.com/lunatic-solutions/lunatic As it is, we’re fortunate to have quite a few great languages and platforms these days.
- drogus 4y agoYou should also check out Gleam! https://gleam.run/ https://gleam.run/ It's almost like Rust and Elixir had a baby :D As for the rest of the comment: totally. Almost none of the code I write in Rust needs C-like performance and I still choose Rust for it.
- ptato 4y ago> I think a lot of Rust users would argue they don’t even care much about performance. They just enjoy all of the correctness guarantees the compiler can enforce Aren't these correctness guarantees only for performance related factors though? (allocation/memory and concurrency). The rest of your program would be just as correct in any other language.
- coder543 4y agoNo. A ton of languages don’t support proper Sum Types, and Rust’s emphasis on errors-as-values helps you think about error handling, instead of only thinking about the happy path. Rust also doesn’t do implicit type coercion and a host of other things that can cause correctness issues. Rust gives you the tools to express more of what you’re doing to the compiler than a lot of languages, which lets the compiler help you more. It’s natural that a lot of programs have some form of concurrency, so that’s an extremely common thing for Rust to help with, but it’s not the only thing.
- ssokolow 4y agoNot really. For me, the number-one Rust feature I love is that, by baking monadic optionality and error return in from the beginning (Option<T> and Result<T, E>), I can trust that, unless an author abuses panic! (in which case I never trust their code again), I can see all a function's return paths in its type signature. (Without having to choke down a pure functional language like Haskell with that currying-based function call syntax that I can never get used to.) The runners-up are how fast Rust starts compared to Python or Java or similar when writing a CLI tool and how nice PyO3 makes safely writing libraries or backends for tools that need to be in Python for some reason like "nothing but PyQt, PySide and possibly QtJambi offers memory-safe QWidget bindings... and I've never found a Java app that didn't feel laggy and sluggish on X11". See also https://cliffle.com/blog/rust-typestate/ https://cliffle.com/blog/rust-typestate/
- marcosdumay 4y ago> in a typical language you can easily pass by reference between ... threads Yeah. And that is almost always an error on those languages too.
- pclmulqdq 4y agoIt's often not. Many high- performance libraries use fine- grained locking within logical units (eg locking buckets within a hash table in the fast path rather than locking the table), which almost necessitates sharing references.
- marcosdumay 4y agoSo, you keep the hash table read-only on a static context, and only mutates some internal references? In rust you will have to declare it exactly like that. That's not really an example of passing mutable references between threads.
- deleted 4y ago[deleted]
- deleted 4y ago[deleted]
- drogus 4y ago> in a typical language you can easily pass by reference between components and threads. and that's when you usually get data races ;) > In rust the unspoken rule seems to be to allocate, allocate, allocate - unless you are writing a special purpose library etc. Which makes me wonder, is rust actually faster than a GC'd language when doing heavy async work? I think it's a bit more nuanced. `Arc` is still a "smart pointer", so while it's not a straight Rust reference, it acts as a pointer. Yes, it allocates and it needs to do a reference count, but the overhead is very small. So while in practice it is not "zero cost", it's usually negligible.
- lijogdfljk 4y agoYea, i definitely agree with this author more than the last. Also, as both a writer of apps and libraries, i agree libraries pose more opportunity to drag yourself deeply into generic relationships and hyper optimizations. Strangely i haven't had many of the issues that the previous poster was discussing, though. My issues are usually trying to work around the lack of GATs, lack of trait aliasing, etc. But i use `async_trait` so maybe i'm sidestepping many of the issues from the previous post. /shrug
- JoshTriplett 4y agoAbsolutely agreed. You can get so caught up in trying to make it perfect that you don't ship something. https://raw.githubusercontent.com/luser/keep-calm-and-call-clone/master/keep-calm-and-call-clone.png https://raw.githubusercontent.com/luser/keep-calm-and-call-c...
- mcronce 4y ago> I can see how it might be easy to get nerd-sniped trying to get rid of every last allocation Honestly, this is a really good way to put it, and that one minute rule sounds like a pretty good rule (ignoring cases where performance requirements led to profiling, which pointed you at some specific piece that you need to optimize, obviously)
- marcosdumay 4y ago> I set a rule for myself that I'll spend up to one minute trying to save an allocation. Beyond that it's not worth getting sidetracked. My rule is that I'll do what is the easiest, unless it's an inner loop that runs all the time, where I'll try to make it fast. In rust, usually the easiest is to not allocate or copy data. When it's easier to allocate or copy, I don't do a cost-benefit analysis at all, I just do it.
- bombela 4y agoI am like a moth irresistibly attracted by the far away light of zero allocation. I can almost reach it. Just one more little lifetime annotation. One more...
- whatshisface 4y agoYou can return structs to an allocation pool ring buffer by writing a custom implementation of the Drop trait. If you do that, almost anything can be zero-allocation. In Zig you can control the allocator as a first-class citizen and, I think, are meant to do things like that.
- kaba0 4y agoHopefully allocators will soon be available in stable Rust as well - then you can optionally specify an Allocator for a given allocation.
- titaniczero 4y agoThis is similar to Go’s sync.Pool to reuse buffers and prevent allocations when you want to optimize things, right?
- bombela 4y agoYes, this is similar. In Rust you control yourself the allocation on the stack or the heap. So before reaching out to a memory pool/arena, it's too tempting trying to put everything on the stack for "maximum performance".
- substation13 4y ago> Remember that almost every language puts almost everything on the heap True, but then those languages are heavily optimized for that scenario. This is why Rust written like Java often performs worse than Java (and optimized Rust).
- whatshisface 4y agoMy rules of thumb for Rust, that for the most part keep me out of allocation puzzles, are: - Never combine two things that can be valid for different amounts of time into one struct. For example: A file struct should not contain both information about how it is formatted, which is true forever and can be used for many files, and a file handle, which could be invalidated by the OS at any time. Breaking this rule will fill your code with Box/Arc as you try to imitate classical OOP. - Don't be afraid to frequently pass contextual information to functions; you don't need to put everything that will remain the same between two calls into `self`. For example, every function that works with the file can take the file handle and the format information as separate arguments. Trying to DRY function arguments by combining data with different lifetimes into a single struct and then hiding that argument in the `self` parameter might feel like simplification, but in Rust it triggers the above problem. - Functions that call functions that take mutable references as output locations should do the same, unless they have to allocate for another reason. This rule of thumb will tend to push allocation as far outside of loops as possible. With these in hand, I almost never need to box or reference count anything. If you fail to heed the fact that Rust is not really an OOP language, your entire program will start to look like the hairy parts of C libraries that interface with the Python interpreter.
- ssokolow 4y ago> If you fail to heed the fact that Rust is not really an OOP language, your entire program will start to look like the hairy parts of C libraries that interface with the Python interpreter. Any tips for those of us who see PyO3 as one of Rust's biggest killer apps? (Honest question. I hate how unmaintainable Python is but I don't know of any better equivalent to PyQt/PySide's memory-safe QWidget bindings or the RAD-friendly ORM migrations in Django ORM or SQLAlchemy+Alembic.)
- whatshisface 4y agoI imagine it would go like writing a good C library for Python, where the PyResult<> wrappers disappear as you move deeper into your code and away from the interface. Hopefully working with Python objects on the outside won't require using references everywhere on the inside.
- deleted 4y ago[deleted]
- Klonoar 4y ago>I set a rule for myself that I'll spend up to one minute trying to save an allocation. Beyond that it's not worth getting sidetracked. This! Though I modified my rule to mostly being "I'm punting this to the weekend as a fun exercise", since I often find stupid enjoyment in seeing if it's possible. Programming in Rust can often be a very good exercise in telling yourself to stop trying to be clever.