6 ms·
> Safety and speed One thing I can't seem to find a quick answer to from the Rust crowed is how does Rust handle dynamic lifetimes? It seems like it does not;
by hsn915 4y ago
> Safety and speed
One thing I can't seem to find a quick answer to from the Rust crowed is how does Rust handle dynamic lifetimes? It seems like it does not; it simply prevents you from referring to objects that have a dynamic lifetime not known at compile time.
You either have to use 'unsafe' or use the 'handles' pattern where you have what amounts to a custom allocator but the compiler does not know that it's an allocator so it can't prevent 'use after free' bugs.
I suppose you can also use Rc, but isn't that essentially a garbage collection scheme, with similar performance characteristics? (AFAICT a reference counting scheme must be able to nullify all references to an object when it's deallocated, so deallocations can become arbitrarily expensive).
Am I missing something?
- Fiahil 4y ago> dynamic lifetimes What do you mean ? If you refer to heap allocations, then you can use Box, Arc, Rc. They are not a "garbage collector" nor do they incur performance hits other than a regular heap allocation.
- eptcyka 4y agoAtomic reference counting does incur a slowdown.
- Fiahil 4y agoyes, but that's not a JVM-like slowdown. It's fairly well amortised and _clearly_ not a matter of consideration when writing an app or a library. If your issue is "atomics are slowing down my app", then I assume you already milked the code to the latest micro second of performances everywhere else. This is likely not the case here and not a general advice I would give to anyone. Remember, some dev in python where concurrency is inexistent, start-up time is horrendous and performances are abysmal compared to rust. (this is exaggerated: of course you can run stuff in parallel in python)
- selfmodruntime 4y agoWhich should be incredibly negligible unless you're counting literal millions of objects. Even then, I'm suspecting that refcounting will never be in the top spots of things that slow you down.
- eptcyka 4y agoWhat I'm saying is that bumping atomic references inside a hot loop will be detrimental for performance, and since it invalidates a cache line and this can flush caches for lots of cores. It also shuts down ILP.
- insanitybit 4y agoOne really nice thing about Rust's use of Arc is that you don't need to bump atomic references in a hot loop, or much at all usually. Once you have an Arc<T> you can `as_ref()` to get a &T. So maybe you need an Arc to share something with another thread, so you clone it once. Once you're in that thread though you can go back to just using `&` and never touch the atomic again.
- moonchild 4y agoReference counting is much slower than 'garbage collection'.
- anonymous_sorry 4y agoReference for "much" slower? Surely depends on usage? The cost is also more predictable/amortised than classic garbage collection.
- scratcheee 4y agoIt's generally true to be fair, reference counting could be used for every garbage collected language (and be much simpler). The only reason they switched to more complex schemes is they're faster on average. Even smart schemes that try to remove unnecessary ref count changes will tend to underperform compared to a (well built) tracing GC. As for a reference, https://en.wikipedia.org/wiki/Tracing_garbage_collection#Performance https://en.wikipedia.org/wiki/Tracing_garbage_collection#Per... The point about predictability is totally valid though (and combined with simplicity is the reason many languages still pick ref counting).
- marcosdumay 4y agoIt's not generally true. It's true for the case when the runtime keeps track of every little memory segment.
- masklinn 4y agoThat's an issue when every object is managed. Which is not the case in an unmanaged language, you'd only refcount what you need to refcount. Furthermore Rust can also safely borrow from a refcounted pointer without the need for refcount traffic, which can be quite the performance gain for atomic refcounts (it's nigh irrelevant for non-thread-safe refcounts as those just do a local increment/decrement).
- scratcheee 4y agoTechnically yes, but if you only use it when you need it, it's often not a performance concideration. And for systems programming, the often overlooked truth is that people care far less about perfect performance than they do about control - ref counting doesn't give up control to a mysterious oracle running in the background which may or may not wreck your performance in hard to predict ways, it just pays a known cost at the time you use it based on how you're using it. (that's not to say performance is irrelevant, but it's not always the top concern, and with control you can always rewrite slow code as needed). The obvious question is "why can't we have an optional modern garbage collector built into a systems language?", and it's a good question (I remember reading there was one in rust for a while during early development, but it got removed), I think the main reason is that high quality garbage collectors are incredibly complicated, with many trade offs, and a gc that combines well with the rest of a language and various alternative memory tracking solutions is harder than most. The projects that really want one can always implement their own and choose their own tradeoffs, so there's not many use-cases where a generic language-provided one would justify the complexity of implementing it within the language.
- thombles 4y ago> AFAICT a reference counting scheme must be able to nullify all references to an object when it's deallocated, so deallocations can become arbitrarily expensive The idea of reference-counted object is that by the time it's deallocated there _are_ no references left to update. Now you might reasonably be asking about the memory left behind by those old references which contain now-invalid pointers. The use-after-free risks of any single given reference to the object is something the Rust compiler can reason about quite easily. Once you drop one of them (and the refcount decrements) you are guaranteed not to be able to get at that pointer again; not until that memory is reinitialised with something else valid.
- hsn915 4y agoWell, there's a concept of a _weak_ reference. I don't know whether it exists in Rust but I can't imaigne a modern reference counting system without support for it. A weak reference does not prevent the object from being deallocated, but the system guarantees that when the object is deallocated, all weak references to it will be nullified. This is what makes deallocating objects in a refcounting system potentially arbitrarily expensive.
- masklinn 4y ago> Well, there's a concept of a _weak_ reference. I don't know whether it exists in Rust It does: - https://doc.rust-lang.org/std/rc/struct.Weak.html https://doc.rust-lang.org/std/rc/struct.Weak.html - https://doc.rust-lang.org/std/sync/struct.Weak.html https://doc.rust-lang.org/std/sync/struct.Weak.html > A weak reference does not prevent the object from being deallocated, but the system guarantees that when the object is deallocated, all weak references to it will be nullified. > This is what makes deallocating objects in a refcounting system potentially arbitrarily expensive. Depends how you implement weakrefs. In Rust, when you upgrade a weakref it checks if there are outstanding strong references, and if there are not the control block is considered invalid and the upgrade fails. There is no need to touch any of the weakrefs at any point. And unlike languages like Python, Rust does not have weakref finalizers either. Deallocation is still arbitrarily expensive in general, as the entire subtree will get deallocated recursively, but that has nothing to do with refcounting.
- nicoburns 4y agoI think you've covered all the major options: - Use Rc (or Arc if you need to be thread-safe) - Use a Vec, slotmap, or a similar data-structure and store handles (indexes). This doesn't entirely prevent use-after-free bugs. But it does tend to make them panics rather than silent errors. - Use unsafe (and ideally create your own higher-level safe abstraction) In theory, there should also be 4: - Use a GC library But I haven't seen a good implementation of this yet. --- If you're missing something, it's perhaps that most lifetimes aren't dynamic. So you get all the benefits of Rust's ownership in the 80% case. And in the 20% case you don't get any particular benefits, but you still have all the same options available to you as in other languages.
- cyber_kinetist 4y agoBut most of the bugs I often face and seem to struggle with are with dynamic lifetimes (in graphics / simulation / gamedev). So even as a relatively low-level systems dev I don’t really find Rust’s borrow checker that useful.
- insanitybit 4y agoI think there are cases where borrow checking is difficult for a domain, but without a lot more detail it's hard to say. My understanding is that graphics/gamedev is one of those cases and if you aren't willing to use ECS you may have problems.
- cyber_kinetist 4y agoI am personally using generational indices for a lot of my stuff, no smart pointers. Many ECS implementations also seem to use this technique. But if you're stuck with legacy code in $WORK you are stuck with raw/smart pointers and these really have lots of issues.
- nicoburns 4y agoI guess the benefit of Rust in this case is that you will get an index out of bounds error rather than Undefined Behaviour.
- creata 4y agoYes, your options are unsafety, garbage collection (in the broad sense, which includes Rc), and hiding lifetimes from the compiler via "handles" or similar. Can you even imagine other options, though? If enough information about the lifetimes isn't known at compile time, how can the compiler prove it safe?
- hsn915 4y agoIt's very rare that a serious problem can be solved without some objects having some sort of a dynamic lifetime that you have to manage. Rust just prevents you from doing this. So you can say it provides safety, but it also restricts you from solving the vast majority of serious problems that we write programs to solve. This is very different from static vs dynamic typing. In a statically typed language, the compiler actually _knows_ what you can and can't do with each object. Where as Rust's life time management just says "Nope, I have no idea whether what you are doing is safe or not so I'm just going to tell you you can't do it".