Part of the problem is that Rust itself has such lofty aspirations to do it all (particularly "abstraction without overhead"), and is largely successful at that. So if I compromise on efficiency, it feels like my code is unworthy of the language it's written in.
Also, it's easy to feel that the slightest inefficiency puts us on a slippery slope to the bloat and slowness of something like Electron. I'd like to fight back against that bloat, the upgrade treadmill, and the rapid obsolescence that serves hardware makers but hurts poor people. On the one hand, there was good software in the 80s and 90s that made liberal use of heap-allocated, reference-counted objects with dynamic dispatch. On the other hand, there were slow, bloated desktop applications before the rise of Java, C#, Electron, etc. So I don't know where the right balance is at.
Right but you dont have todo this everywhere, just some sporadic use where it makes life a lot easier. It doesn't have to be all or nothing.
F# is probably what you're looking for if you want functional-lite programming with a strong type system, but don't want to deal with the pains caused by static resource management.
If you want to use .NET, F# is a fine choice. But otherwise, and especially in the context of an alternative to Rust, I recommend OCaml.
[0] https://docs.microsoft.com/en-us/dotnet/fsharp/get-started/i...
https://github.com/ionide/Ionide-vim
Meanwhile it has one of the fastest concurrent+GC runtimes, obliterating Ocaml in this regard, and competing handily with JVM or CLR langs.
Finally, it has standard abstractions for concurrency and parallelism that are basically flawless.
Clearly, the hate comes from the association of Haskell with category theory, which is frankly useless/borderline harmful to the working Haskell programmer.
CT was critical to core library devs being able to deliver the main workaday abstractions that make Haskell such an unreasonably productive app languages. But since 2012 you can simply use these main abstractions and enjoy a level of safety and maintainability that rust, c++, ocaml, kotlin, java can't touch.
It's not perfect, but there's been huge improvements of late to tooling (cabal) so there's never been a better time. I really wish the tide could turn, because if you're willing to take on rust for low-level, you're missing out if you don't try haskell for everything else server-side.
You can have the best of both worlds: In many cases, I simply put an object into an Arc, clone that a bunch of times, and pass/store it to wherever it's needed. Then, within loops, I pass a reference to local function calls (instead of cloning the Arc for every call). For any given v: Arc<T>, doing &*v is zero-cost – unlike in many other languages, where you'd have to pass the Arc itself, which would involve atomic increments/decrements without escape analysis.
How it'd be designed and implemented is probably a theme for a separate blog post, not a HN comment.
And then the O in OCaml is all about OOP.
I want to write `toString (1,true)` not `ToStringPair(ToStringInt)(ToStringBool).toString (1,true)`.
Traits, maybe? Switch to an OO model more closely aligned to what the majority of developers understand. Dump all of the line-noise-type syntax.
My Own Toy Language (ComingRealSoonNow)^tm that I started designing had exactly one goal - prevent the majority of memory errors, not prevent ALL memory errors. All I wanted was to indicate when/where an object will be destroyed/mutated. That's enough for me.
Also the basis for one programming language that used to be widespread in the enterprise for quick and dirty solutions, VB and its ecosystem of OCX libraries.
It is more widespread that people think, because when many argue about OOP, they miss the full spectrum of how OOP is approached.
So? Giving up Rust-Traits doesn't mean that I will give up on interfaces as a whole.
I don't like the way C++ does interfaces, for example, but that doesn't mean that my ideal language won't have interfaces.
F# to the rescue. :)
Just make the allocations.
Even if you write it that way it's still significantly more performant and lightweight than the alternatives without much loss in productivity.
I've seen a lot of new people also prematurely over generalizing code. I know it sounds terrible to say, but if you probably aren't going to reuse it, you really don't have to prepare your code base just in case you might later. Decide that later and move along...
This reminds me: "you are holding it wrong"
You're trying to draw a comparison with a consumer product with a design flaw.
Not at all. I just do not like to dance around bonfire with tam-tam and being told that this is the one and only way
Agreed. Copy, Clone, Box, Arc, RwLock, etc. are your friends. Don't be afraid to use them.
You don't need as much performance as you think you do. Passing things around on the heap is fine for most applications. The Rust compiler is often smarter than you think.
And, when the Rust compiler is dumb or your code is getting stuck, you have code that you can understand and find the hotspot of. Now you can be clever.
The important part is perspective. Nobody cares if you clone your command line arguments ten times during startup. As Dijkstra said, optimize the 2% of your program that matter, keep the rest simple
I'm using Rust since ~2 months. At the beginning I was trying to write my usual C/C++ code and I got completely entangled with lifetimes relationships and the code ended up becoming a mess.
Nowadays as soon as the compiler starts mentioning lifetimes I see that as a warning => I then take a step back and change approach/design => no problems.
I use a little bit of async (I create dedicated variables that are moved to the async functions) and classic multithreading (I use "mpsc" to exchange data between the main & subthreads) and so far I never had a single segfault nor any kind of weird behaviour, which is incredible if compared to some other languages at least for my programming skills :)
I've taken to making heavy use of the smallvec and smartstring crates for this. Most lists and strings are small in practice. Using smallvec / smartstring lets you keep most clone() calls allocation-free. This in turn lets you use owned objects, which are easier to reason about - for you and the borrow checker. And you keep a lot of the performance of just passing around references.
I tried to use async rust a couple of years ago, and fell on my face in the process. Most of my rust at the moment is designed to compile to wasm - and then I'm leaning on nodejs for networking and IO. Writing async networked code is oh so much easier to reason about in javascript. When GAT, TAIT and some other language features to fix async land I'll muster up the courage to make another attempt. But rust's progress at fixing these problems feels painfully slow.
https://crates.io/crates/smallvec / https://crates.io/crates/smartstring
Rust is normally used only when high performance is of uttermost importance, so it will always attract people who want to optmise everything.
> Most of the time, the solution is to be less clever
I've done it myself and saw performance degrade, as was expected. That's fine, but by that point you might as well just use another language that has none of the hard problems and end up having very similar performance, which is what I did.
Which is not the way to do things. Profile, then optimize.
I'm writing a metaverse client that's heavily multithreaded and can keep a GPU, a dozen CPUs, and a network connection busy. Only some parts have to go fast. The critical parts are:
* The render loop, which is in its own higher-priority thread.
* Blocking the render loop with locks set during GPU content updating, which is supposed to be done in parallel with rendering.
* JPEG 2000 decoding, which eats up too much time and for which 10x faster decoders are available.
* Strategies for deciding which content to load first.
* Strategies for deciding what doesn't have to be drawn.
Those really matter. The rest is either minor, infrequent, or not on the critical path.
I use Tracy to let me watch and zoom in on where the time goes in each rendered frame. Unless Tracy says performance is a problem, it doesn't need to be optimized.
The Rust game dev ecosystem is far enough along for simple games, but not there yet when you need all the performance of which the hardware is capable.
I have trouble with concept of WGPU. GPUs are complex by themselves to bolt on top any abstraction that is coming from Web. But its just me, its not important since I am not a 3d programmer myself. I am more Engine / CPU optimization guy.
My interest in Rust and this topic is that i would like to see fine grained task parallel systems written in Rust. Instead of systems with separate thread for render that became a bottleneck years ago. I wish you good luck and hope to see a success story about Rust.
WGPU's API is basically Vulkan. It exists mostly to deal with Apple's Metal. Metal has roughly the same feature set as Vulkan, but Apple just had to Think Different and be incompatible. I'm not supporting the Wasm or Android targets. Android and browsers have a different threading model, and I don't want to deal with that at this stage. Linux/Windows/Mac is enough for now.
Thought for the near future - will VR and AR headgear have threads or something more like the processes with some shared shared memory model from Javascript land?
(That video isn't me, it's the Rend3 dev, who also works on WGPU.)
I dont work with VR/AR myself so I dont know.
Even in regular Rust, trying to get too clever with lifetimes can cause serious pain. The usual culprit is complex code that tries to never allocate memory. "Oh, well this closure borrows this parameter from the parent function, and then stores a reference to it in this stack-based structure inside the closure, and then we pass everything by reference to this higher order function..." Just say no.
If you add async to the mix, then you need to keep your ownership simple. If you have a long-running async function, then pass parameters by value! If you have a polymorphic async function, then return your result in a Box. (This also breaks up the generated state machine used to implement async functions, and can reduce binary size.)
So much of this pain is caused by premature optimization.
In C++, if you get to clever, you eventually make a mistake and segfault. In Rust, if you get too clever, it eventually becomes impossible to satisfy the borrow checker. Most of the time, the solution is to be less clever.
Async Rust was a fascinating experiment, but in practice, I think it turned out to be a tool that's best used conservatively. I've written some very stable and high-performance production code using async Rust. But I keep it simple when I can.