Earlier quoted context omitted.
> The lifetime of an Arc isn’t unknowable, it’s determined by where and how you hold it. In the same sense that the lifetime of an object in a GC'd system has a lower bound of, "as long as it's referenced", sure. But that's nearly the opposite of what the borrow checker tries to do by statically bounding objects, at compile time. > maybe the disconnect in this article is that the author is coming at Rust and trying t…
> In the same sense that the lifetime of an object in a GC'd system has a lower bound of, "as long as it's referenced", sure. These are not the same. The problem with GC'd systems is that you don't know when the GC will run and eat up your cpu cycles. It is impossible to determine when the memory will actually be freed in such systems. With ARC, you know exactly when you will release your last reference and that's wh…
Maybe Rust isn’t a good tool for massively concurrent, userspace software
511–520 of 624 posts
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#512Earlier quoted context omitted.
The dev cycle is slower, yes, but once it compiles, there is no debug cycle.
I have found someone that never introduces logic errors, and found out a way to use dependent types in Rust. /s
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#513OK, I suppose I should write to this. As I've mentioned before, I'm writing a high performance metaverse client. Here's a demo video.[1] It's about 40,000 lines of Rust so far. If you are doing a non-crappy metaverse, which is rare, you need to wrangle a rather excessive amount of data in near real time. In games, there's heavy optimization during game development to prevent overloading the play engine. In a metavers…
Is there a ML to subscribe to, to learn when the viewer is more generally available for testing? Thanks again!
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#514Earlier quoted context omitted.
> Async traits come to mind immediately, I agree that being able to use `async` inside of traits would be very useful, and hopefully we will get it soon. > generally needing more capability to existentially quantify Future types without penalty Could you clarify what you mean by that? Both `impl Future` and `dyn Future` exist, do they not work for your use case? > Async function types are a mess to write out. Are you…
> That would be useful, but I wouldn't call the lack of it "half-baked", since no other mainstream language has it either. It's just a nice-to-have. Golang supports running asynchronous code in defers, similar with Zig when it still had async. Async-drop gets upgraded from a nice-to-have into an efficiency concern as the current scheme of "finish your cancellation in Drop" doesn't support borrowed memory in completio…
So does Rust. You can run async code inside `drop`.
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#515Earlier quoted context omitted.
> Yes, sometimes you can get stuck for a day, trying to express something within the ownership rules. This is a big problem. Fast iteration time is very valuable. And who likes doing this to themselves anyway? Isn't it a very frustrating experience? How is this the most loved language?
> How is this the most loved language? Personal preference and pain tolerance. Just like learning Emacs[1] - there's lots of things that programmers can prioritize, ignore, enjoy, or barely tolerate. Some people are alright with the fact that they're prototyping their code 10x more slowly than in another language because they enjoy performance optimization and seeing their code run fast, and there's nothing wrong wit…
Disclaimer: I've sort of bounced off of Rust 3 or so times and while I've created both long-running services in it as well as smaller tools I've basically mostly had a hard time (not enjoying it at all, feeling like I'm paying a lot in terms of development friction for very little gain, etc.) and if you're the type to write off most posts with "You just don't get it" this would probably just be one more on the pile. I would argue that I do understand the value of Rust, but I take issue with the idea that the cost is worth it in the majority of cases, and I think that there are 80% solutions that work better in practice for most cases.
From personal experience: You could be prototyping your code faster and get performance in simpler ways than dealing with the borrow checker by being able to express allocation patterns and memory usage in better, clearer ways instead and avoid both of the stated problems.
Odin (& Zig and other simpler languages) with access to these types of facilities are just an install away and are considerably easier to learn anyway. In fact, I think you could probably just learn both of them on top of what you're doing in Rust since the time investment is negligible compared to it in the long run.
With regards to the upsides in terms of writing code in a performance-aware manner:
- It's easier to look at a piece of code and confidently say it's not doing any odd or potentially bad things with regards to performance in both Odin and Zig
- Both languages emphasize custom allocators which are a great boon to both application simplicity, flexibility and performance (set up limited memory space temporarily and make sure we can never use more, set up entire arenas that can be reclaimed or reused entirely, segment your resources up in different allocators that can't possibly interfere with eachother and have their own memory space guaranteed, etc.)
- No one can use one-at-a-time constructs like RAII/`Drop` behind your back so you don't have to worry about stupid magic happening when things go out of scope that might completely ruin your cache, etc.
To borrow an argument from Rust proponents, you should be thinking about these things (allocation patterns) anyway and you're doing yourself a disservice by leaving them up to magic or just doing them wrong. If your language can't do what Odin and Zig does (pass them around, and in Odin you can inherit them from the calling scope which coupled with passing them around gives you incredible freedom) then you probably should try one where you can and where the ecosystem is based on that assumption.
My personal experience with first Zig and later Odin is that they've provided the absolute most productive experience I've ever had when it comes to the code that I had to write. I had to write more code because both ecosystems are tiny and I don't really like extra dependencies regardless. Being able to actually write your dependencies yourself but have it be such a productive experience is liberating in so many ways.
Odin is my personal winner in the race between Odin and Zig. It's a very close race but there are some key features in Odin that make it win out in the end:
- There is an implicit `context` parameter primarily used for passing around an allocator, a temp-allocator and a logger that can be implicitly used for calls if you don't specify one. This makes your code less chatty and let's you talk only about the important things in some cases. I still prefer to be explicit about allocators in most plumbing but I'll set `context.allocator` to some appropriate choice for smaller programs in `main` and let it go
- We can have proper tagged unions as errors and the language is built around it. This gives you code that looks and behaves a lot like you'll be used to with `Result` and `Option` in Rust, with the same benefits.
- Errors are just values but the last value in a multiple-value-return function is understood as the error position if needed so we avoid the `if error != nil { ... }` that would otherwise exist if the language wasn't made for this. We can instead use proper error values (that can be tagged unions) and `or_return`, i.e.:
doing_things :: proc() ParsingError {
parsed_data := parse_config_file(filename) or_return
...
}
If we wanted to inspect the error this would instead be: // The zero value for a union is `nil` by default and the language understands this
ParsingError :: union {
UnparsableHeader,
UnparsableBody,
}
UnparsableHeader :: struct {
...
}
UnparsableBody :: struct {
...
}
doing_things :: proc() {
parsed_data, parsing_error := parse_config_file(filename)
// `p in parsing_error` here unpacks the tag of the union
// Notably there are no actual "constructors" like in Haskell
// and so a type can be part of many different unions with no syntax changes
// for checking for it.
switch p in parsing_error {
case UnparsableHeader:
// In this scope we have an `UnparsableHeader`
function_that_deals_with_unparsable_header(p)
case UnparsableBody:
function_that_deals_with_unparsable_body(p)
}
...
}
- ZVI or "zero-value initialization" means that all values are by default zero-initialized and have to have zero-values. The entire language and ecosystem is built around this idea and it works terrifically to allow you to actually talk only about the things that are important, once again.P.S. If you want to make games or the like Odin has the absolute best ecosystem of any C alternative or C++ alternative out there, no contest. Largely this is because it ships with tons of game related bindings and also has language features dedicated entirely to dealing with vectors, matrices, etc., and is a joy to use for those things. I'd still put it forward as a winner with regards to most other areas but it really is an unfair race when it comes to games.
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#516Earlier quoted context omitted.
> There was an article on HN a few days ago about this. "Rust has 5 games and 50 game engines". That's not a serious article. That's a humourous video. Source: https://youtu.be/TGfQu0bQTKc?t=169
It has some truth to it, still.
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#517Earlier quoted context omitted.
> The thing is, these dependencies do exist no matter what language you use Sure, but in a lot of cases, these invariants can be trivially explained, or intuitive enough that it wouldn't even need explanation. While in Rust, you can easily spend a full day just explaining it to the compiler. I remember spending litteral _days_ tweaking intricate lifetimes and scopes just to promise Rust that some variables won't be u…
>use fancy smart pointers, etc. The thing is, you think your code is safe and it most likely is, but mathematically speaking, what you are doing is difficult or even impossible to prove correct. It is akin to running an NP complete algorithm on a problem that is easier than NP. Most practical problem instances are easy to solve, but the worst case which can't be ruled out is utterly, utterly terrible, which forces yo…
Since smart pointers because ubiquitous in c++, I've (personally) had only a handful of memory and lifetime issues. They were all deduceable by looking at where we "escape hatched" and stored a raw ptr that was actually a unique pointer, or something similar. I'll take having one of those every 18 months over throwing away my entire language, toolchain,ecosystem and iteration times.
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#518Earlier quoted context omitted.
It stays with you until you need to change something and find yourself unable to make incremental changes. And in many use cases people are throwing Rust (and especially async Rust) on problems solved just fine with GC languages so the safety argument doesn’t apply there.
The safety argument is actually the reason why you can use Rust in those cases to begin with. If it was C or C++ you simply couldn't use it for things like webservers due to the safety problems inherent to these languages. So Rust creeps into the part of the market that used to be exclusive to GC languages.
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#519Earlier quoted context omitted.
Rust Futures are essentially green threads, except much lighter-weight, much faster, and implemented in user space instead of being built-in to the language. Basically Rust Futures is what Go wishes it could have. Rust made the right choice in waiting and spending the time to design async right .
You're overstating your case. Rust's async tasks (based on stackless coroutines) and Go's goroutines (based on stackful coroutines) have important differences. Rust's design introduces function coloring (tentative solution in progress) but is much more suited for the bare-metal scene that C and C++ are famous for. Go's design has more overhead but, by virtue of not having colored functions, is simpler for programmers…
Colored functions is a debatable problem at best. I consider it a feature not a bug and it makes reasoning about programs easier at the expense of writing additional async/await keywords which is really a very minor annoyance.
On the other hand Go's need of using channels to do trivial and common tasks like communicating the result of an async task together with the lack of RAII and proper cleanup signaling in channels (you can very easily deadlock if nothing is attached on the other end of the channel), plus no compile time race detection - all that makes writing concurrent code harder.
Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software
#520Earlier quoted context omitted.
Async traits come to mind immediately, generally needing more capability to existentially quantify Future types without penalty. Async function types are a mess to write out. More control over heap allocations in async/await futures (we currently have to Box/Pin more often than necessary). Async drop. Better cancellation. Async iteration.
> Async traits come to mind immediately, I agree that being able to use `async` inside of traits would be very useful, and hopefully we will get it soon. > generally needing more capability to existentially quantify Future types without penalty Could you clarify what you mean by that? Both `impl Future` and `dyn Future` exist, do they not work for your use case? > Async function types are a mess to write out. Are you…
It'd be very nice to be able to use `impl` in more locations, representing a type which needs not be known to the user but is constant. This is a common occurrence and may let us write code like `fn foo(f: impl Fn() -> impl Future)` or maybe even eventually syntax sugar like `fn foo(f: impl async Fn())` which would be ideal.
Re: Boxing
I find that a common technique needed to get make abstraction around futures to work is the need to Box::pin things regularly. This isn't always an issue, but it's frequent enough that it's annoying. Moreover, it's not strictly necessary given knowledge of the future type, it's again more of a matter of Rust's minimal existential types.
Re: async drop and cancellation.
It's not always possible to have good guarantees about the cleanup of resources in async contexts. You can use abort, but that will just cause the the next yield point to not return and then the Drops to run. So now you're reliant on Drops working. I usually build in a "kind" shutdown with a timer before aborting in light of this.
C# has a version of this with their CancelationTokens. They're possible to get wrong and it's easy to fail to cancel promptly, but by convention it's also easy to pass a cancelation request and let tasks do resource cleanup before dying.
Re: Async iteration
Nicer syntax is definitely the thing. Futures without async/await also could just be done with combinators, but at the same time it wasn't popular or easy until the syntax was in place. I think there's a lot of leverage in getting good syntax and exploring the space of streams more fully.