WUFFS
WUFFS
Posted Aug 16, 2024 12:54 UTC (Fri) by excors (subscriber, #95769)In reply to: WUFFS by paulj
Parent article: Standards for use of unsafe Rust in the kernel
Depends what you mean by "better". I think the main differences are that tracing GC typically has better long-term-average performance and can handle cyclic data structures, while refcounting has more predictable performance and can have useful destructors.
The average performance is a bigger deal in languages like Python or Java where almost everything is heap-allocated and GCed, even objects that only ever have a single owner, so a lot of time would be spent needlessly updating refcounts (especially with multithreading where they need to be atomic updates). It should matter less in Rust where the programmer can choose to use Rc only for the relatively rare objects with non-trivial ownership, and the more expensive atomic Arc for the even rarer ones that are shared between threads (and Rust guarantees you'll never accidentally share a non-atomic Rc between threads). That does require some extra thought from the programmer, though.
It also takes some extra thought to avoid cyclic data structures (maybe using weak references etc), or to explicitly break cycles when destroying the container, to avoid memory leaks. (Python solves it by using reference counting plus a tracing GC to find cycles, but then you get most of the downsides of both.)
Tracing GC has much less predictable performance because it might pause all your threads, or at least eat up a lot of CPU time, whenever it arbitrarily feels like it. For batch processing, that doesn't matter. For moderately latency-sensitive tasks (like web services), you might end up spending significant effort tuning the GC heuristics to minimise pauses. For real-time, no.
It's pretty nice having destructors that are guaranteed to run immediately when the last reference to an object is dropped (which tracing GC can't do) - you can use RAII for objects that hold non-memory resources (like file handles) that the GC doesn't know about, without worrying that you'll suffer resource exhaustion while the GC thinks you've still got plenty of memory and there's no need to collect any garbage. Particularly useful for a systems programming language since you're often working with resources provided by the system, outside of the GC's scope.
The tradeoffs chosen by Rust wouldn't work for every language; but for a systems programming language aimed at relatively sophisticated programmers, I think it has made a good choice.
Posted Aug 16, 2024 13:07 UTC (Fri)
by mb (subscriber, #50428)
[Link] (3 responses)
Yeah, it's ok-ish most of the time.
Except for the case of move-capturing blocks. Passing an Arc clone to multiple async-move blocks is awkward.
I think this is on the current list of things to be improved. But I'm not sure what an elegant solution would look like.
Posted Aug 16, 2024 19:12 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link] (2 responses)
[1] https://smallcultfollowing.com/babysteps/blog/2024/06/21/...
Posted Aug 16, 2024 20:49 UTC (Fri)
by khim (subscriber, #9252)
[Link] (1 responses)
I actually dislike that proposal. Because it's conceptually wrong. It asserts that the only difference between To me, on the congnitive level, The whole thing is solving entirely wrong problem: if the desire is to replace this:
This would make it possible for a people like me to enable a clippy lint which rejects such code (I very much do want to see when I'm sharing ownership with some other task), although I can see why some people would want to add some magic to their programs. Thankfully this proposal also comes with IOW: IMNSHO this proposal is a net negative, but very mild one, not enough to throw a temper tantrum if it would be accepted.
Posted Aug 16, 2024 21:28 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link]
Except I want it for plain closures too, not just `async` blocks.
WUFFS
It basically means cloning the Arc to a different name and then moving that name into the async block.
Which typically results in names such as foo2 or foo_cloned.
(Name shadowing is also possible in some cases.)
WUFFS
WUFFS
copy
and clone
is efficiency. But in reality difference is on another level: Copy types are just chunks of memory without internal structure and their copy is entirely independent from the original while clone
means something else.clone
of Rc<Vec<u32>>
means that we want to split ownership of that [unmodifiable] vector… which maybe is kinda-sorta-Ok (because it's fixed and unmodifiable), but I would definitely not want to see Rc<RefCell<Vec<u32>>>
automatically clones and, if understand correctly, this proposal would make it automatically clone
'able, too.
tokio::spawn({
let io = cx.io.clone():
let disk = cx.disk.clone():
let health_check = cx.health_check.clone():
async move {
do_something(io, disk, health_check)
}
})
with this:
tokio::spawn(async move {
do_something(cx.io, cx.disk, cx.health_check)
})
then it can be fixed with addition of just one keyword (it may even not be a keyword since it always comes after async
):
tokio::spawn(async clone {
do_something(cx.io, cx.disk, cx.health_check)
})
#![deny(automatic_claims)]
which is good enough for me: while complexity of that whole madness is a bit larger than I would like with existence of #![deny(automatic_claims)]
I don't care all that much about what other people are doing about Claim
.WUFFS
>
> tokio::spawn(async clone {
> do_something(cx.io, cx.disk, cx.health_check)
> })